eventsourcing 9.4.0a5__py3-none-any.whl → 9.4.0a6__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 +21 -27
- eventsourcing/domain.py +49 -51
- eventsourcing/interface.py +5 -2
- eventsourcing/persistence.py +29 -36
- eventsourcing/popo.py +17 -16
- eventsourcing/postgres.py +18 -17
- eventsourcing/projection.py +8 -17
- eventsourcing/sqlite.py +18 -17
- eventsourcing/system.py +49 -62
- eventsourcing/tests/application.py +3 -3
- eventsourcing/tests/persistence.py +12 -12
- eventsourcing/utils.py +8 -20
- {eventsourcing-9.4.0a5.dist-info → eventsourcing-9.4.0a6.dist-info}/METADATA +2 -4
- eventsourcing-9.4.0a6.dist-info/RECORD +26 -0
- eventsourcing-9.4.0a5.dist-info/RECORD +0 -26
- {eventsourcing-9.4.0a5.dist-info → eventsourcing-9.4.0a6.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.4.0a5.dist-info → eventsourcing-9.4.0a6.dist-info}/LICENSE +0 -0
- {eventsourcing-9.4.0a5.dist-info → eventsourcing-9.4.0a6.dist-info}/WHEEL +0 -0
eventsourcing/persistence.py
CHANGED
|
@@ -4,27 +4,15 @@ import json
|
|
|
4
4
|
import uuid
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from collections import deque
|
|
7
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from datetime import datetime
|
|
9
10
|
from decimal import Decimal
|
|
10
11
|
from queue import Queue
|
|
11
12
|
from threading import Condition, Event, Lock, Semaphore, Thread, Timer
|
|
12
13
|
from time import monotonic, sleep, time
|
|
13
|
-
from types import ModuleType
|
|
14
|
-
from typing import
|
|
15
|
-
TYPE_CHECKING,
|
|
16
|
-
Any,
|
|
17
|
-
Dict,
|
|
18
|
-
Generic,
|
|
19
|
-
Iterator,
|
|
20
|
-
List,
|
|
21
|
-
Mapping,
|
|
22
|
-
Sequence,
|
|
23
|
-
Type,
|
|
24
|
-
TypeVar,
|
|
25
|
-
Union,
|
|
26
|
-
cast,
|
|
27
|
-
)
|
|
14
|
+
from types import GenericAlias, ModuleType
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast
|
|
28
16
|
from uuid import UUID
|
|
29
17
|
from warnings import warn
|
|
30
18
|
|
|
@@ -78,8 +66,8 @@ class JSONTranscoder(Transcoder):
|
|
|
78
66
|
"""
|
|
79
67
|
|
|
80
68
|
def __init__(self) -> None:
|
|
81
|
-
self.types:
|
|
82
|
-
self.names:
|
|
69
|
+
self.types: dict[type, Transcoding] = {}
|
|
70
|
+
self.names: dict[str, Transcoding] = {}
|
|
83
71
|
self.encoder = json.JSONEncoder(
|
|
84
72
|
default=self._encode_obj,
|
|
85
73
|
separators=(",", ":"),
|
|
@@ -106,7 +94,7 @@ class JSONTranscoder(Transcoder):
|
|
|
106
94
|
"""
|
|
107
95
|
return self.decoder.decode(data.decode("utf8"))
|
|
108
96
|
|
|
109
|
-
def _encode_obj(self, o: Any) ->
|
|
97
|
+
def _encode_obj(self, o: Any) -> dict[str, Any]:
|
|
110
98
|
try:
|
|
111
99
|
transcoding = self.types[type(o)]
|
|
112
100
|
except KeyError:
|
|
@@ -122,7 +110,7 @@ class JSONTranscoder(Transcoder):
|
|
|
122
110
|
"_data_": transcoding.encode(o),
|
|
123
111
|
}
|
|
124
112
|
|
|
125
|
-
def _decode_obj(self, d:
|
|
113
|
+
def _decode_obj(self, d: dict[str, Any]) -> Any:
|
|
126
114
|
if len(d) == 2:
|
|
127
115
|
try:
|
|
128
116
|
_type_ = d["_type_"]
|
|
@@ -314,7 +302,7 @@ class Mapper:
|
|
|
314
302
|
stored_state = self.cipher.decrypt(stored_state)
|
|
315
303
|
if self.compressor:
|
|
316
304
|
stored_state = self.compressor.decompress(stored_state)
|
|
317
|
-
event_state:
|
|
305
|
+
event_state: dict[str, Any] = self.transcoder.decode(stored_state)
|
|
318
306
|
event_state["originator_id"] = stored_event.originator_id
|
|
319
307
|
event_state["originator_version"] = stored_event.originator_version
|
|
320
308
|
cls = resolve_topic(stored_event.topic)
|
|
@@ -422,7 +410,7 @@ class AggregateRecorder(ABC):
|
|
|
422
410
|
|
|
423
411
|
@abstractmethod
|
|
424
412
|
def insert_events(
|
|
425
|
-
self, stored_events:
|
|
413
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
426
414
|
) -> Sequence[int] | None:
|
|
427
415
|
"""
|
|
428
416
|
Writes stored events into database.
|
|
@@ -437,7 +425,7 @@ class AggregateRecorder(ABC):
|
|
|
437
425
|
lte: int | None = None,
|
|
438
426
|
desc: bool = False,
|
|
439
427
|
limit: int | None = None,
|
|
440
|
-
) ->
|
|
428
|
+
) -> list[StoredEvent]:
|
|
441
429
|
"""
|
|
442
430
|
Reads stored events from database.
|
|
443
431
|
"""
|
|
@@ -468,7 +456,7 @@ class ApplicationRecorder(AggregateRecorder):
|
|
|
468
456
|
topics: Sequence[str] = (),
|
|
469
457
|
*,
|
|
470
458
|
inclusive_of_start: bool = True,
|
|
471
|
-
) ->
|
|
459
|
+
) -> list[Notification]:
|
|
472
460
|
"""
|
|
473
461
|
Returns a list of Notification objects representing events from an
|
|
474
462
|
application sequence. If `inclusive_of_start` is True (the default),
|
|
@@ -598,7 +586,7 @@ class EventStore:
|
|
|
598
586
|
|
|
599
587
|
def put(
|
|
600
588
|
self, domain_events: Sequence[DomainEventProtocol], **kwargs: Any
|
|
601
|
-
) ->
|
|
589
|
+
) -> list[Recording]:
|
|
602
590
|
"""
|
|
603
591
|
Stores domain events in aggregate sequence.
|
|
604
592
|
"""
|
|
@@ -670,14 +658,14 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
670
658
|
|
|
671
659
|
@classmethod
|
|
672
660
|
def construct(
|
|
673
|
-
cls:
|
|
661
|
+
cls: type[TInfrastructureFactory], env: Environment | None = None
|
|
674
662
|
) -> TInfrastructureFactory:
|
|
675
663
|
"""
|
|
676
664
|
Constructs concrete infrastructure factory for given
|
|
677
665
|
named application. Reads and resolves persistence
|
|
678
666
|
topic from environment variable 'PERSISTENCE_MODULE'.
|
|
679
667
|
"""
|
|
680
|
-
factory_cls:
|
|
668
|
+
factory_cls: type[InfrastructureFactory[TrackingRecorder]]
|
|
681
669
|
if env is None:
|
|
682
670
|
env = Environment()
|
|
683
671
|
topic = (
|
|
@@ -696,7 +684,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
696
684
|
or "eventsourcing.popo"
|
|
697
685
|
)
|
|
698
686
|
try:
|
|
699
|
-
obj:
|
|
687
|
+
obj: type[InfrastructureFactory[TrackingRecorder]] | ModuleType = (
|
|
700
688
|
resolve_topic(topic)
|
|
701
689
|
)
|
|
702
690
|
except TopicError as e:
|
|
@@ -709,15 +697,20 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
709
697
|
|
|
710
698
|
if isinstance(obj, ModuleType):
|
|
711
699
|
# Find the factory in the module.
|
|
712
|
-
factory_classes:
|
|
700
|
+
factory_classes: list[type[InfrastructureFactory[TrackingRecorder]]] = []
|
|
713
701
|
for member in obj.__dict__.values():
|
|
714
702
|
if (
|
|
715
703
|
member is not InfrastructureFactory
|
|
716
704
|
and isinstance(member, type) # Look for classes...
|
|
705
|
+
and isinstance(member, type) # Look for classes...
|
|
706
|
+
and not isinstance(
|
|
707
|
+
member, GenericAlias
|
|
708
|
+
) # Issue with Python 3.9 and 3.10.
|
|
717
709
|
and issubclass(member, InfrastructureFactory) # Ignore base class.
|
|
718
710
|
and member not in factory_classes # Forgive aliases.
|
|
719
711
|
):
|
|
720
712
|
factory_classes.append(member)
|
|
713
|
+
|
|
721
714
|
if len(factory_classes) == 1:
|
|
722
715
|
factory_cls = factory_classes[0]
|
|
723
716
|
else:
|
|
@@ -747,7 +740,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
747
740
|
"""
|
|
748
741
|
transcoder_topic = self.env.get(self.TRANSCODER_TOPIC)
|
|
749
742
|
if transcoder_topic:
|
|
750
|
-
transcoder_class:
|
|
743
|
+
transcoder_class: type[Transcoder] = resolve_topic(transcoder_topic)
|
|
751
744
|
else:
|
|
752
745
|
transcoder_class = JSONTranscoder
|
|
753
746
|
return transcoder_class()
|
|
@@ -755,7 +748,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
755
748
|
def mapper(
|
|
756
749
|
self,
|
|
757
750
|
transcoder: Transcoder | None = None,
|
|
758
|
-
mapper_class:
|
|
751
|
+
mapper_class: type[Mapper] | None = None,
|
|
759
752
|
) -> Mapper:
|
|
760
753
|
"""
|
|
761
754
|
Constructs a mapper.
|
|
@@ -783,7 +776,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
783
776
|
cipher_topic = default_cipher_topic
|
|
784
777
|
|
|
785
778
|
if cipher_topic:
|
|
786
|
-
cipher_cls:
|
|
779
|
+
cipher_cls: type[Cipher] = resolve_topic(cipher_topic)
|
|
787
780
|
cipher = cipher_cls(self.env)
|
|
788
781
|
|
|
789
782
|
return cipher
|
|
@@ -796,7 +789,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
796
789
|
compressor: Compressor | None = None
|
|
797
790
|
compressor_topic = self.env.get(self.COMPRESSOR_TOPIC)
|
|
798
791
|
if compressor_topic:
|
|
799
|
-
compressor_cls:
|
|
792
|
+
compressor_cls: type[Compressor] | Compressor = resolve_topic(
|
|
800
793
|
compressor_topic
|
|
801
794
|
)
|
|
802
795
|
if isinstance(compressor_cls, type):
|
|
@@ -832,7 +825,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
832
825
|
|
|
833
826
|
@abstractmethod
|
|
834
827
|
def tracking_recorder(
|
|
835
|
-
self, tracking_recorder_class:
|
|
828
|
+
self, tracking_recorder_class: type[TTrackingRecorder] | None = None
|
|
836
829
|
) -> TTrackingRecorder:
|
|
837
830
|
"""
|
|
838
831
|
Constructs a tracking recorder.
|
|
@@ -1013,7 +1006,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1013
1006
|
self.max_age = max_age
|
|
1014
1007
|
self.pre_ping = pre_ping
|
|
1015
1008
|
self._pool: deque[TConnection] = deque()
|
|
1016
|
-
self._in_use:
|
|
1009
|
+
self._in_use: dict[int, TConnection] = {}
|
|
1017
1010
|
self._get_semaphore = Semaphore()
|
|
1018
1011
|
self._put_condition = Condition()
|
|
1019
1012
|
self._no_readers = Condition()
|
|
@@ -1362,9 +1355,9 @@ class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
|
|
|
1362
1355
|
) -> None:
|
|
1363
1356
|
super().__init__(recorder=recorder, gt=gt, topics=topics)
|
|
1364
1357
|
self._select_limit = 500
|
|
1365
|
-
self._notifications:
|
|
1358
|
+
self._notifications: list[Notification] = []
|
|
1366
1359
|
self._notifications_index: int = 0
|
|
1367
|
-
self._notifications_queue: Queue[
|
|
1360
|
+
self._notifications_queue: Queue[list[Notification]] = Queue(maxsize=10)
|
|
1368
1361
|
self._has_been_notified = Event()
|
|
1369
1362
|
self._thread_error: BaseException | None = None
|
|
1370
1363
|
self._pull_thread = Thread(target=self._loop_on_pull)
|
eventsourcing/popo.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import contextlib
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
from threading import Event, Lock
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
8
|
from eventsourcing.persistence import (
|
|
9
9
|
AggregateRecorder,
|
|
@@ -21,6 +21,7 @@ from eventsourcing.persistence import (
|
|
|
21
21
|
from eventsourcing.utils import resolve_topic, reversed_keys
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Iterable, Sequence
|
|
24
25
|
from uuid import UUID
|
|
25
26
|
|
|
26
27
|
|
|
@@ -32,23 +33,23 @@ class POPORecorder:
|
|
|
32
33
|
class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
|
|
33
34
|
def __init__(self) -> None:
|
|
34
35
|
super().__init__()
|
|
35
|
-
self._stored_events:
|
|
36
|
-
self._stored_events_index:
|
|
36
|
+
self._stored_events: list[StoredEvent] = []
|
|
37
|
+
self._stored_events_index: dict[UUID, dict[int, int]] = defaultdict(dict)
|
|
37
38
|
|
|
38
39
|
def insert_events(
|
|
39
|
-
self, stored_events:
|
|
40
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
40
41
|
) -> Sequence[int] | None:
|
|
41
42
|
self._insert_events(stored_events, **kwargs)
|
|
42
43
|
return None
|
|
43
44
|
|
|
44
45
|
def _insert_events(
|
|
45
|
-
self, stored_events:
|
|
46
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
46
47
|
) -> Sequence[int] | None:
|
|
47
48
|
with self._database_lock:
|
|
48
49
|
self._assert_uniqueness(stored_events, **kwargs)
|
|
49
50
|
return self._update_table(stored_events, **kwargs)
|
|
50
51
|
|
|
51
|
-
def _assert_uniqueness(self, stored_events:
|
|
52
|
+
def _assert_uniqueness(self, stored_events: list[StoredEvent], **_: Any) -> None:
|
|
52
53
|
new = set()
|
|
53
54
|
for s in stored_events:
|
|
54
55
|
# Check events don't already exist.
|
|
@@ -62,7 +63,7 @@ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
|
|
|
62
63
|
raise IntegrityError(msg)
|
|
63
64
|
|
|
64
65
|
def _update_table(
|
|
65
|
-
self, stored_events:
|
|
66
|
+
self, stored_events: list[StoredEvent], **_: Any
|
|
66
67
|
) -> Sequence[int] | None:
|
|
67
68
|
notification_ids = []
|
|
68
69
|
for s in stored_events:
|
|
@@ -81,7 +82,7 @@ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
|
|
|
81
82
|
lte: int | None = None,
|
|
82
83
|
desc: bool = False,
|
|
83
84
|
limit: int | None = None,
|
|
84
|
-
) ->
|
|
85
|
+
) -> list[StoredEvent]:
|
|
85
86
|
with self._database_lock:
|
|
86
87
|
results = []
|
|
87
88
|
|
|
@@ -103,10 +104,10 @@ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
|
|
|
103
104
|
class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
|
|
104
105
|
def __init__(self) -> None:
|
|
105
106
|
super().__init__()
|
|
106
|
-
self._listeners:
|
|
107
|
+
self._listeners: set[Event] = set()
|
|
107
108
|
|
|
108
109
|
def insert_events(
|
|
109
|
-
self, stored_events:
|
|
110
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
110
111
|
) -> Sequence[int] | None:
|
|
111
112
|
notification_ids = self._insert_events(stored_events, **kwargs)
|
|
112
113
|
self._notify_listeners()
|
|
@@ -120,7 +121,7 @@ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
|
|
|
120
121
|
topics: Sequence[str] = (),
|
|
121
122
|
*,
|
|
122
123
|
inclusive_of_start: bool = True,
|
|
123
|
-
) ->
|
|
124
|
+
) -> list[Notification]:
|
|
124
125
|
with self._database_lock:
|
|
125
126
|
results = []
|
|
126
127
|
if start is None:
|
|
@@ -192,8 +193,8 @@ class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
|
|
|
192
193
|
class POPOTrackingRecorder(POPORecorder, TrackingRecorder):
|
|
193
194
|
def __init__(self) -> None:
|
|
194
195
|
super().__init__()
|
|
195
|
-
self._tracking_table:
|
|
196
|
-
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)
|
|
197
198
|
|
|
198
199
|
def _assert_tracking_uniqueness(self, tracking: Tracking) -> None:
|
|
199
200
|
if tracking.notification_id in self._tracking_table[tracking.application_name]:
|
|
@@ -227,7 +228,7 @@ class POPOProcessRecorder(
|
|
|
227
228
|
POPOTrackingRecorder, POPOApplicationRecorder, ProcessRecorder
|
|
228
229
|
):
|
|
229
230
|
def _assert_uniqueness(
|
|
230
|
-
self, stored_events:
|
|
231
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
231
232
|
) -> None:
|
|
232
233
|
super()._assert_uniqueness(stored_events, **kwargs)
|
|
233
234
|
t: Tracking | None = kwargs.get("tracking", None)
|
|
@@ -235,7 +236,7 @@ class POPOProcessRecorder(
|
|
|
235
236
|
self._assert_tracking_uniqueness(t)
|
|
236
237
|
|
|
237
238
|
def _update_table(
|
|
238
|
-
self, stored_events:
|
|
239
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
239
240
|
) -> Sequence[int] | None:
|
|
240
241
|
notification_ids = super()._update_table(stored_events, **kwargs)
|
|
241
242
|
t: Tracking | None = kwargs.get("tracking", None)
|
|
@@ -252,7 +253,7 @@ class POPOFactory(InfrastructureFactory[POPOTrackingRecorder]):
|
|
|
252
253
|
return POPOApplicationRecorder()
|
|
253
254
|
|
|
254
255
|
def tracking_recorder(
|
|
255
|
-
self, tracking_recorder_class:
|
|
256
|
+
self, tracking_recorder_class: type[POPOTrackingRecorder] | None = None
|
|
256
257
|
) -> POPOTrackingRecorder:
|
|
257
258
|
if tracking_recorder_class is None:
|
|
258
259
|
tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
|
eventsourcing/postgres.py
CHANGED
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
from asyncio import CancelledError
|
|
5
5
|
from contextlib import contextmanager
|
|
6
6
|
from threading import Thread
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Callable
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
8
8
|
|
|
9
9
|
import psycopg
|
|
10
10
|
import psycopg.errors
|
|
@@ -37,6 +37,7 @@ from eventsourcing.persistence import (
|
|
|
37
37
|
from eventsourcing.utils import Environment, resolve_topic, retry, strtobool
|
|
38
38
|
|
|
39
39
|
if TYPE_CHECKING:
|
|
40
|
+
from collections.abc import Iterator, Sequence
|
|
40
41
|
from uuid import UUID
|
|
41
42
|
|
|
42
43
|
from typing_extensions import Self
|
|
@@ -190,7 +191,7 @@ class PostgresRecorder:
|
|
|
190
191
|
self.datastore = datastore
|
|
191
192
|
self.create_table_statements = self.construct_create_table_statements()
|
|
192
193
|
|
|
193
|
-
def construct_create_table_statements(self) ->
|
|
194
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
194
195
|
return []
|
|
195
196
|
|
|
196
197
|
@staticmethod
|
|
@@ -247,11 +248,11 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
|
|
|
247
248
|
self.select_events_statement = (
|
|
248
249
|
f"SELECT * FROM {self.events_table_name} WHERE originator_id = %s"
|
|
249
250
|
)
|
|
250
|
-
self.lock_table_statements:
|
|
251
|
+
self.lock_table_statements: list[str] = []
|
|
251
252
|
|
|
252
253
|
@retry((InterfaceError, OperationalError), max_attempts=10, wait=0.2)
|
|
253
254
|
def insert_events(
|
|
254
|
-
self, stored_events:
|
|
255
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
255
256
|
) -> Sequence[int] | None:
|
|
256
257
|
conn: Connection[DictRow]
|
|
257
258
|
exc: Exception | None = None
|
|
@@ -283,7 +284,7 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
|
|
|
283
284
|
def _insert_events(
|
|
284
285
|
self,
|
|
285
286
|
c: Cursor[DictRow],
|
|
286
|
-
stored_events:
|
|
287
|
+
stored_events: list[StoredEvent],
|
|
287
288
|
**_: Any,
|
|
288
289
|
) -> None:
|
|
289
290
|
pass
|
|
@@ -291,7 +292,7 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
|
|
|
291
292
|
def _insert_stored_events(
|
|
292
293
|
self,
|
|
293
294
|
c: Cursor[DictRow],
|
|
294
|
-
stored_events:
|
|
295
|
+
stored_events: list[StoredEvent],
|
|
295
296
|
**_: Any,
|
|
296
297
|
) -> None:
|
|
297
298
|
# Only do something if there is something to do.
|
|
@@ -324,7 +325,7 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
|
|
|
324
325
|
def _fetch_ids_after_insert_events(
|
|
325
326
|
self,
|
|
326
327
|
c: Cursor[DictRow],
|
|
327
|
-
stored_events:
|
|
328
|
+
stored_events: list[StoredEvent],
|
|
328
329
|
**kwargs: Any,
|
|
329
330
|
) -> Sequence[int] | None:
|
|
330
331
|
return None
|
|
@@ -338,9 +339,9 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
|
|
|
338
339
|
lte: int | None = None,
|
|
339
340
|
desc: bool = False,
|
|
340
341
|
limit: int | None = None,
|
|
341
|
-
) ->
|
|
342
|
+
) -> list[StoredEvent]:
|
|
342
343
|
statement = self.select_events_statement
|
|
343
|
-
params:
|
|
344
|
+
params: list[Any] = [originator_id]
|
|
344
345
|
if gt is not None:
|
|
345
346
|
params.append(gt)
|
|
346
347
|
statement += " AND originator_version > %s"
|
|
@@ -413,13 +414,13 @@ class PostgresApplicationRecorder(PostgresAggregateRecorder, ApplicationRecorder
|
|
|
413
414
|
topics: Sequence[str] = (),
|
|
414
415
|
*,
|
|
415
416
|
inclusive_of_start: bool = True,
|
|
416
|
-
) ->
|
|
417
|
+
) -> list[Notification]:
|
|
417
418
|
"""
|
|
418
419
|
Returns a list of event notifications
|
|
419
420
|
from 'start', limited by 'limit'.
|
|
420
421
|
"""
|
|
421
422
|
|
|
422
|
-
params:
|
|
423
|
+
params: list[int | str | Sequence[str]] = []
|
|
423
424
|
statement = f"SELECT * FROM {self.events_table_name}"
|
|
424
425
|
has_where = False
|
|
425
426
|
if start is not None:
|
|
@@ -508,10 +509,10 @@ class PostgresApplicationRecorder(PostgresAggregateRecorder, ApplicationRecorder
|
|
|
508
509
|
def _fetch_ids_after_insert_events(
|
|
509
510
|
self,
|
|
510
511
|
c: Cursor[DictRow],
|
|
511
|
-
stored_events:
|
|
512
|
+
stored_events: list[StoredEvent],
|
|
512
513
|
**kwargs: Any,
|
|
513
514
|
) -> Sequence[int] | None:
|
|
514
|
-
notification_ids:
|
|
515
|
+
notification_ids: list[int] = []
|
|
515
516
|
len_events = len(stored_events)
|
|
516
517
|
if len_events:
|
|
517
518
|
while c.nextset() and len(notification_ids) != len_events:
|
|
@@ -667,7 +668,7 @@ class PostgresProcessRecorder(
|
|
|
667
668
|
def _insert_events(
|
|
668
669
|
self,
|
|
669
670
|
c: Cursor[DictRow],
|
|
670
|
-
stored_events:
|
|
671
|
+
stored_events: list[StoredEvent],
|
|
671
672
|
**kwargs: Any,
|
|
672
673
|
) -> None:
|
|
673
674
|
tracking: Tracking | None = kwargs.get("tracking", None)
|
|
@@ -893,7 +894,7 @@ class PostgresFactory(InfrastructureFactory[PostgresTrackingRecorder]):
|
|
|
893
894
|
|
|
894
895
|
application_recorder_topic = self.env.get(self.APPLICATION_RECORDER_TOPIC)
|
|
895
896
|
if application_recorder_topic:
|
|
896
|
-
application_recorder_class:
|
|
897
|
+
application_recorder_class: type[PostgresApplicationRecorder] = (
|
|
897
898
|
resolve_topic(application_recorder_topic)
|
|
898
899
|
)
|
|
899
900
|
assert issubclass(application_recorder_class, PostgresApplicationRecorder)
|
|
@@ -909,7 +910,7 @@ class PostgresFactory(InfrastructureFactory[PostgresTrackingRecorder]):
|
|
|
909
910
|
return recorder
|
|
910
911
|
|
|
911
912
|
def tracking_recorder(
|
|
912
|
-
self, tracking_recorder_class:
|
|
913
|
+
self, tracking_recorder_class: type[PostgresTrackingRecorder] | None = None
|
|
913
914
|
) -> PostgresTrackingRecorder:
|
|
914
915
|
prefix = self.env.name.lower() or "notification"
|
|
915
916
|
tracking_table_name = prefix + "_tracking"
|
|
@@ -942,7 +943,7 @@ class PostgresFactory(InfrastructureFactory[PostgresTrackingRecorder]):
|
|
|
942
943
|
|
|
943
944
|
process_recorder_topic = self.env.get(self.PROCESS_RECORDER_TOPIC)
|
|
944
945
|
if process_recorder_topic:
|
|
945
|
-
process_recorder_class:
|
|
946
|
+
process_recorder_class: type[PostgresTrackingRecorder] = resolve_topic(
|
|
946
947
|
process_recorder_topic
|
|
947
948
|
)
|
|
948
949
|
assert issubclass(process_recorder_class, PostgresProcessRecorder)
|
eventsourcing/projection.py
CHANGED
|
@@ -3,19 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import weakref
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Iterator, Sequence
|
|
6
7
|
from threading import Event, Thread
|
|
7
8
|
from traceback import format_exc
|
|
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
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
19
10
|
from warnings import warn
|
|
20
11
|
|
|
21
12
|
from eventsourcing.application import Application
|
|
@@ -34,7 +25,7 @@ if TYPE_CHECKING:
|
|
|
34
25
|
from typing_extensions import Self
|
|
35
26
|
|
|
36
27
|
|
|
37
|
-
class ApplicationSubscription(Iterator[
|
|
28
|
+
class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
|
|
38
29
|
"""
|
|
39
30
|
An iterator that yields all domain events recorded in an application
|
|
40
31
|
sequence that have notification IDs greater than a given value. The iterator
|
|
@@ -64,7 +55,7 @@ class ApplicationSubscription(Iterator[Tuple[DomainEventProtocol, Tracking]]):
|
|
|
64
55
|
def __iter__(self) -> Self:
|
|
65
56
|
return self
|
|
66
57
|
|
|
67
|
-
def __next__(self) ->
|
|
58
|
+
def __next__(self) -> tuple[DomainEventProtocol, Tracking]:
|
|
68
59
|
notification = next(self.subscription)
|
|
69
60
|
tracking = Tracking(self.name, notification.id)
|
|
70
61
|
domain_event = self.mapper.to_domain_event(notification)
|
|
@@ -109,9 +100,9 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
|
|
|
109
100
|
def __init__(
|
|
110
101
|
self,
|
|
111
102
|
*,
|
|
112
|
-
application_class:
|
|
113
|
-
projection_class:
|
|
114
|
-
tracking_recorder_class:
|
|
103
|
+
application_class: type[TApplication],
|
|
104
|
+
projection_class: type[Projection[TTrackingRecorder]],
|
|
105
|
+
tracking_recorder_class: type[TTrackingRecorder] | None = None,
|
|
115
106
|
env: EnvType | None = None,
|
|
116
107
|
):
|
|
117
108
|
self.app: TApplication = application_class(env)
|
|
@@ -151,7 +142,7 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
|
|
|
151
142
|
"""
|
|
152
143
|
Constructs environment from which projection will be configured.
|
|
153
144
|
"""
|
|
154
|
-
_env:
|
|
145
|
+
_env: dict[str, str] = {}
|
|
155
146
|
_env.update(os.environ)
|
|
156
147
|
if env is not None:
|
|
157
148
|
_env.update(env)
|
eventsourcing/sqlite.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sqlite3
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
from eventsourcing.persistence import (
|
|
@@ -32,6 +32,7 @@ from eventsourcing.persistence import (
|
|
|
32
32
|
from eventsourcing.utils import Environment, resolve_topic, strtobool
|
|
33
33
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
35
|
+
from collections.abc import Iterator, Sequence
|
|
35
36
|
from types import TracebackType
|
|
36
37
|
|
|
37
38
|
SQLITE3_DEFAULT_LOCK_TIMEOUT = 5
|
|
@@ -103,7 +104,7 @@ class SQLiteTransaction:
|
|
|
103
104
|
|
|
104
105
|
def __exit__(
|
|
105
106
|
self,
|
|
106
|
-
exc_type:
|
|
107
|
+
exc_type: type[BaseException] | None,
|
|
107
108
|
exc_val: BaseException | None,
|
|
108
109
|
exc_tb: TracebackType | None,
|
|
109
110
|
) -> None:
|
|
@@ -254,7 +255,7 @@ class SQLiteRecorder(Recorder):
|
|
|
254
255
|
self.datastore = datastore
|
|
255
256
|
self.create_table_statements = self.construct_create_table_statements()
|
|
256
257
|
|
|
257
|
-
def construct_create_table_statements(self) ->
|
|
258
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
258
259
|
return []
|
|
259
260
|
|
|
260
261
|
def create_table(self) -> None:
|
|
@@ -278,7 +279,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
278
279
|
f"SELECT * FROM {self.events_table_name} WHERE originator_id=? "
|
|
279
280
|
)
|
|
280
281
|
|
|
281
|
-
def construct_create_table_statements(self) ->
|
|
282
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
282
283
|
statements = super().construct_create_table_statements()
|
|
283
284
|
statements.append(
|
|
284
285
|
"CREATE TABLE IF NOT EXISTS "
|
|
@@ -294,7 +295,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
294
295
|
return statements
|
|
295
296
|
|
|
296
297
|
def insert_events(
|
|
297
|
-
self, stored_events:
|
|
298
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
298
299
|
) -> Sequence[int] | None:
|
|
299
300
|
with self.datastore.transaction(commit=True) as c:
|
|
300
301
|
return self._insert_events(c, stored_events, **kwargs)
|
|
@@ -302,7 +303,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
302
303
|
def _insert_events(
|
|
303
304
|
self,
|
|
304
305
|
c: SQLiteCursor,
|
|
305
|
-
stored_events:
|
|
306
|
+
stored_events: list[StoredEvent],
|
|
306
307
|
**_: Any,
|
|
307
308
|
) -> Sequence[int] | None:
|
|
308
309
|
params = [
|
|
@@ -325,9 +326,9 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
325
326
|
lte: int | None = None,
|
|
326
327
|
desc: bool = False,
|
|
327
328
|
limit: int | None = None,
|
|
328
|
-
) ->
|
|
329
|
+
) -> list[StoredEvent]:
|
|
329
330
|
statement = self.select_events_statement
|
|
330
|
-
params:
|
|
331
|
+
params: list[Any] = [originator_id.hex]
|
|
331
332
|
if gt is not None:
|
|
332
333
|
statement += "AND originator_version>? "
|
|
333
334
|
params.append(gt)
|
|
@@ -369,7 +370,7 @@ class SQLiteApplicationRecorder(
|
|
|
369
370
|
f"SELECT MAX(rowid) FROM {self.events_table_name}"
|
|
370
371
|
)
|
|
371
372
|
|
|
372
|
-
def construct_create_table_statements(self) ->
|
|
373
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
373
374
|
statement = (
|
|
374
375
|
"CREATE TABLE IF NOT EXISTS "
|
|
375
376
|
f"{self.events_table_name} ("
|
|
@@ -385,7 +386,7 @@ class SQLiteApplicationRecorder(
|
|
|
385
386
|
def _insert_events(
|
|
386
387
|
self,
|
|
387
388
|
c: SQLiteCursor,
|
|
388
|
-
stored_events:
|
|
389
|
+
stored_events: list[StoredEvent],
|
|
389
390
|
**_: Any,
|
|
390
391
|
) -> Sequence[int] | None:
|
|
391
392
|
returning = []
|
|
@@ -410,12 +411,12 @@ class SQLiteApplicationRecorder(
|
|
|
410
411
|
topics: Sequence[str] = (),
|
|
411
412
|
*,
|
|
412
413
|
inclusive_of_start: bool = True,
|
|
413
|
-
) ->
|
|
414
|
+
) -> list[Notification]:
|
|
414
415
|
"""
|
|
415
416
|
Returns a list of event notifications
|
|
416
417
|
from 'start', limited by 'limit'.
|
|
417
418
|
"""
|
|
418
|
-
params:
|
|
419
|
+
params: list[int | str] = []
|
|
419
420
|
statement = f"SELECT rowid, * FROM {self.events_table_name} "
|
|
420
421
|
has_where = False
|
|
421
422
|
if start is not None:
|
|
@@ -494,7 +495,7 @@ class SQLiteTrackingRecorder(SQLiteRecorder, TrackingRecorder):
|
|
|
494
495
|
"application_name=? AND notification_id=?"
|
|
495
496
|
)
|
|
496
497
|
|
|
497
|
-
def construct_create_table_statements(self) ->
|
|
498
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
498
499
|
statements = super().construct_create_table_statements()
|
|
499
500
|
statements.append(
|
|
500
501
|
"CREATE TABLE IF NOT EXISTS tracking ("
|
|
@@ -552,7 +553,7 @@ class SQLiteProcessRecorder(
|
|
|
552
553
|
def _insert_events(
|
|
553
554
|
self,
|
|
554
555
|
c: SQLiteCursor,
|
|
555
|
-
stored_events:
|
|
556
|
+
stored_events: list[StoredEvent],
|
|
556
557
|
**kwargs: Any,
|
|
557
558
|
) -> Sequence[int] | None:
|
|
558
559
|
returning = super()._insert_events(c, stored_events, **kwargs)
|
|
@@ -616,7 +617,7 @@ class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
|
616
617
|
application_recorder_topic = self.env.get(self.APPLICATION_RECORDER_TOPIC)
|
|
617
618
|
|
|
618
619
|
if application_recorder_topic:
|
|
619
|
-
application_recorder_class:
|
|
620
|
+
application_recorder_class: type[SQLiteApplicationRecorder] = resolve_topic(
|
|
620
621
|
application_recorder_topic
|
|
621
622
|
)
|
|
622
623
|
assert issubclass(application_recorder_class, SQLiteApplicationRecorder)
|
|
@@ -630,7 +631,7 @@ class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
|
630
631
|
return recorder
|
|
631
632
|
|
|
632
633
|
def tracking_recorder(
|
|
633
|
-
self, tracking_recorder_class:
|
|
634
|
+
self, tracking_recorder_class: type[SQLiteTrackingRecorder] | None = None
|
|
634
635
|
) -> SQLiteTrackingRecorder:
|
|
635
636
|
if tracking_recorder_class is None:
|
|
636
637
|
tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
|
|
@@ -653,7 +654,7 @@ class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
|
653
654
|
process_recorder_topic = self.env.get(self.PROCESS_RECORDER_TOPIC)
|
|
654
655
|
|
|
655
656
|
if process_recorder_topic:
|
|
656
|
-
process_recorder_class:
|
|
657
|
+
process_recorder_class: type[SQLiteProcessRecorder] = resolve_topic(
|
|
657
658
|
process_recorder_topic
|
|
658
659
|
)
|
|
659
660
|
assert issubclass(process_recorder_class, SQLiteProcessRecorder)
|