eventsourcing 9.5.0b3__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.
- eventsourcing/__init__.py +0 -0
- eventsourcing/application.py +998 -0
- eventsourcing/cipher.py +107 -0
- eventsourcing/compressor.py +15 -0
- eventsourcing/cryptography.py +91 -0
- eventsourcing/dcb/__init__.py +0 -0
- eventsourcing/dcb/api.py +144 -0
- eventsourcing/dcb/application.py +159 -0
- eventsourcing/dcb/domain.py +369 -0
- eventsourcing/dcb/msgpack.py +38 -0
- eventsourcing/dcb/persistence.py +193 -0
- eventsourcing/dcb/popo.py +178 -0
- eventsourcing/dcb/postgres_tt.py +704 -0
- eventsourcing/dcb/tests.py +608 -0
- eventsourcing/dispatch.py +80 -0
- eventsourcing/domain.py +1964 -0
- eventsourcing/interface.py +164 -0
- eventsourcing/persistence.py +1429 -0
- eventsourcing/popo.py +267 -0
- eventsourcing/postgres.py +1441 -0
- eventsourcing/projection.py +502 -0
- eventsourcing/py.typed +0 -0
- eventsourcing/sqlite.py +816 -0
- eventsourcing/system.py +1203 -0
- eventsourcing/tests/__init__.py +3 -0
- eventsourcing/tests/application.py +483 -0
- eventsourcing/tests/domain.py +105 -0
- eventsourcing/tests/persistence.py +1744 -0
- eventsourcing/tests/postgres_utils.py +131 -0
- eventsourcing/utils.py +257 -0
- eventsourcing-9.5.0b3.dist-info/METADATA +253 -0
- eventsourcing-9.5.0b3.dist-info/RECORD +35 -0
- eventsourcing-9.5.0b3.dist-info/WHEEL +4 -0
- eventsourcing-9.5.0b3.dist-info/licenses/AUTHORS +10 -0
- eventsourcing-9.5.0b3.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from eventsourcing.dcb.api import (
|
|
8
|
+
DCBAppendCondition,
|
|
9
|
+
DCBEvent,
|
|
10
|
+
DCBQuery,
|
|
11
|
+
DCBReadResponse,
|
|
12
|
+
DCBRecorder,
|
|
13
|
+
DCBSequencedEvent,
|
|
14
|
+
)
|
|
15
|
+
from eventsourcing.dcb.persistence import (
|
|
16
|
+
DCBInfrastructureFactory,
|
|
17
|
+
DCBListenNotifySubscription,
|
|
18
|
+
)
|
|
19
|
+
from eventsourcing.persistence import IntegrityError, ProgrammingError
|
|
20
|
+
from eventsourcing.popo import POPOFactory, POPORecorder, POPOTrackingRecorder
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Iterator, Sequence
|
|
24
|
+
from threading import Event
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InMemoryDCBRecorder(DCBRecorder, POPORecorder):
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
super().__init__()
|
|
30
|
+
self.events: list[DCBSequencedEvent] = []
|
|
31
|
+
self.position_sequence = self._position_sequence_generator()
|
|
32
|
+
self._listeners: set[Event] = set()
|
|
33
|
+
|
|
34
|
+
def read(
|
|
35
|
+
self,
|
|
36
|
+
query: DCBQuery | None = None,
|
|
37
|
+
*,
|
|
38
|
+
after: int | None = None,
|
|
39
|
+
limit: int | None = None,
|
|
40
|
+
) -> DCBReadResponse:
|
|
41
|
+
query = query or DCBQuery()
|
|
42
|
+
with self._database_lock:
|
|
43
|
+
events_generator = (
|
|
44
|
+
event
|
|
45
|
+
for event in self.events
|
|
46
|
+
if (after is None or event.position > after)
|
|
47
|
+
and (
|
|
48
|
+
not query.items
|
|
49
|
+
or any(
|
|
50
|
+
(not item.types or event.event.type in item.types)
|
|
51
|
+
and (set(event.event.tags) >= set(item.tags))
|
|
52
|
+
for item in query.items
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
events = []
|
|
58
|
+
for i, event in enumerate(events_generator):
|
|
59
|
+
if limit is not None and i >= limit:
|
|
60
|
+
break
|
|
61
|
+
events.append(deepcopy(event))
|
|
62
|
+
if limit is None:
|
|
63
|
+
head = self.events[-1].position if self.events else None
|
|
64
|
+
else:
|
|
65
|
+
head = events[-1].position if events else None
|
|
66
|
+
# TODO: Change the previous few lines to actually be an iterator.
|
|
67
|
+
return SimpleDCBReadResponse(iter(events), head)
|
|
68
|
+
|
|
69
|
+
def append(
|
|
70
|
+
self, events: Sequence[DCBEvent], condition: DCBAppendCondition | None = None
|
|
71
|
+
) -> int:
|
|
72
|
+
if len(events) == 0:
|
|
73
|
+
msg = "Should be at least one event. Avoid this elsewhere"
|
|
74
|
+
raise ProgrammingError(msg)
|
|
75
|
+
with self._database_lock:
|
|
76
|
+
if condition is not None:
|
|
77
|
+
read_response = self.read(
|
|
78
|
+
query=condition.fail_if_events_match,
|
|
79
|
+
after=condition.after,
|
|
80
|
+
limit=1,
|
|
81
|
+
)
|
|
82
|
+
try:
|
|
83
|
+
next(read_response)
|
|
84
|
+
except StopIteration:
|
|
85
|
+
pass
|
|
86
|
+
else:
|
|
87
|
+
raise IntegrityError(condition)
|
|
88
|
+
self.events.extend(
|
|
89
|
+
DCBSequencedEvent(
|
|
90
|
+
position=next(self.position_sequence),
|
|
91
|
+
event=deepcopy(event),
|
|
92
|
+
)
|
|
93
|
+
for event in events
|
|
94
|
+
)
|
|
95
|
+
self._notify_listeners()
|
|
96
|
+
return self.events[-1].position
|
|
97
|
+
|
|
98
|
+
def _position_sequence_generator(self) -> Iterator[int]:
|
|
99
|
+
position = 1
|
|
100
|
+
while True:
|
|
101
|
+
yield position
|
|
102
|
+
position += 1
|
|
103
|
+
|
|
104
|
+
def subscribe(
|
|
105
|
+
self,
|
|
106
|
+
query: DCBQuery | None = None,
|
|
107
|
+
*,
|
|
108
|
+
after: int | None = None,
|
|
109
|
+
) -> InMemorySubscription:
|
|
110
|
+
return InMemorySubscription(self, query=query, after=after)
|
|
111
|
+
|
|
112
|
+
def listen(self, event: Event) -> None:
|
|
113
|
+
self._listeners.add(event)
|
|
114
|
+
|
|
115
|
+
def unlisten(self, event: Event) -> None:
|
|
116
|
+
with contextlib.suppress(KeyError):
|
|
117
|
+
self._listeners.remove(event)
|
|
118
|
+
|
|
119
|
+
def _notify_listeners(self) -> None:
|
|
120
|
+
for listener in self._listeners:
|
|
121
|
+
listener.set()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class InMemorySubscription(DCBListenNotifySubscription[InMemoryDCBRecorder]):
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
recorder: InMemoryDCBRecorder,
|
|
128
|
+
query: DCBQuery | None = None,
|
|
129
|
+
after: int | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
super().__init__(recorder=recorder, query=query, after=after)
|
|
132
|
+
self._recorder.listen(self._has_been_notified)
|
|
133
|
+
|
|
134
|
+
def stop(self) -> None:
|
|
135
|
+
super().stop()
|
|
136
|
+
self._recorder.unlisten(self._has_been_notified)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SimpleDCBReadResponse(DCBReadResponse):
|
|
140
|
+
def __init__(self, events: Iterator[DCBSequencedEvent], head: int | None = None):
|
|
141
|
+
self.events = events
|
|
142
|
+
self._head_was_given = head is not None
|
|
143
|
+
self._head = head
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def head(self) -> int | None:
|
|
147
|
+
return self._head
|
|
148
|
+
|
|
149
|
+
def __next__(self) -> DCBSequencedEvent:
|
|
150
|
+
event = next(self.events)
|
|
151
|
+
if not self._head_was_given: # pragma: no cover
|
|
152
|
+
self._head = event.position
|
|
153
|
+
return event
|
|
154
|
+
|
|
155
|
+
# def next_batch(self) -> list[DCBSequencedEvent]:
|
|
156
|
+
# """
|
|
157
|
+
# Returns a batch of events as a list.
|
|
158
|
+
# Updates the head position similar to __next__.
|
|
159
|
+
# """
|
|
160
|
+
# result = []
|
|
161
|
+
# max_batch_size = 100
|
|
162
|
+
#
|
|
163
|
+
# # Get up to max_batch_size events from the iterator
|
|
164
|
+
# try:
|
|
165
|
+
# for _ in range(max_batch_size):
|
|
166
|
+
# event = next(self.events)
|
|
167
|
+
# if not self._head_was_given:
|
|
168
|
+
# self._head = event.position
|
|
169
|
+
# result.append(event)
|
|
170
|
+
# except StopIteration:
|
|
171
|
+
# pass
|
|
172
|
+
# return result
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class InMemoryDCBFactory(POPOFactory, DCBInfrastructureFactory[POPOTrackingRecorder]):
|
|
176
|
+
|
|
177
|
+
def dcb_recorder(self) -> DCBRecorder:
|
|
178
|
+
return InMemoryDCBRecorder()
|