eventsourcing 9.4.5__py3-none-any.whl → 9.5.0a0__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 +16 -3
- eventsourcing/dcb/__init__.py +0 -0
- eventsourcing/dcb/api.py +65 -0
- eventsourcing/dcb/application.py +116 -0
- eventsourcing/dcb/domain.py +381 -0
- eventsourcing/dcb/persistence.py +146 -0
- eventsourcing/dcb/popo.py +95 -0
- eventsourcing/dcb/postgres_tt.py +643 -0
- eventsourcing/domain.py +147 -62
- eventsourcing/persistence.py +60 -45
- eventsourcing/popo.py +2 -2
- eventsourcing/postgres.py +355 -132
- eventsourcing/sqlite.py +25 -3
- eventsourcing/tests/application.py +5 -1
- eventsourcing/tests/persistence.py +53 -80
- eventsourcing/tests/postgres_utils.py +59 -1
- eventsourcing/utils.py +7 -3
- {eventsourcing-9.4.5.dist-info → eventsourcing-9.5.0a0.dist-info}/METADATA +2 -2
- eventsourcing-9.5.0a0.dist-info/RECORD +33 -0
- eventsourcing-9.4.5.dist-info/RECORD +0 -26
- {eventsourcing-9.4.5.dist-info → eventsourcing-9.5.0a0.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.4.5.dist-info → eventsourcing-9.5.0a0.dist-info}/LICENSE +0 -0
- {eventsourcing-9.4.5.dist-info → eventsourcing-9.5.0a0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING, Literal, TypeVar, overload
|
|
5
|
+
|
|
6
|
+
from eventsourcing.dcb.api import (
|
|
7
|
+
DCBAppendCondition,
|
|
8
|
+
DCBEvent,
|
|
9
|
+
DCBQuery,
|
|
10
|
+
DCBQueryItem,
|
|
11
|
+
DCBRecorder,
|
|
12
|
+
)
|
|
13
|
+
from eventsourcing.dcb.domain import (
|
|
14
|
+
CanMutateEnduringObject,
|
|
15
|
+
Group,
|
|
16
|
+
Selector,
|
|
17
|
+
)
|
|
18
|
+
from eventsourcing.persistence import InfrastructureFactory, TTrackingRecorder
|
|
19
|
+
from eventsourcing.utils import get_topic
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Sequence
|
|
23
|
+
|
|
24
|
+
TGroup = TypeVar("TGroup", bound=Group)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DCBMapper(ABC):
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def to_dcb_event(self, event: CanMutateEnduringObject) -> DCBEvent:
|
|
30
|
+
raise NotImplementedError # pragma: no cover
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def to_domain_event(self, event: DCBEvent) -> CanMutateEnduringObject:
|
|
34
|
+
raise NotImplementedError # pragma: no cover
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DCBEventStore:
|
|
38
|
+
def __init__(self, mapper: DCBMapper, recorder: DCBRecorder):
|
|
39
|
+
self.mapper = mapper
|
|
40
|
+
self.recorder = recorder
|
|
41
|
+
|
|
42
|
+
def put(
|
|
43
|
+
self,
|
|
44
|
+
*events: CanMutateEnduringObject,
|
|
45
|
+
cb: Selector | Sequence[Selector] | None = None,
|
|
46
|
+
after: int | None = None,
|
|
47
|
+
) -> int:
|
|
48
|
+
if not cb and not after:
|
|
49
|
+
condition = None
|
|
50
|
+
else:
|
|
51
|
+
query = self._cb_to_dcb_query(cb)
|
|
52
|
+
condition = DCBAppendCondition(
|
|
53
|
+
fail_if_events_match=query,
|
|
54
|
+
after=after,
|
|
55
|
+
)
|
|
56
|
+
return self.recorder.append(
|
|
57
|
+
events=[self.mapper.to_dcb_event(e) for e in events],
|
|
58
|
+
condition=condition,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@overload
|
|
62
|
+
def get(
|
|
63
|
+
self,
|
|
64
|
+
cb: Selector | Sequence[Selector] | None = None,
|
|
65
|
+
*,
|
|
66
|
+
after: int | None = None,
|
|
67
|
+
) -> Sequence[CanMutateEnduringObject]:
|
|
68
|
+
pass # pragma: no cover
|
|
69
|
+
|
|
70
|
+
@overload
|
|
71
|
+
def get(
|
|
72
|
+
self,
|
|
73
|
+
cb: Selector | Sequence[Selector] | None = None,
|
|
74
|
+
*,
|
|
75
|
+
with_last_position: Literal[True],
|
|
76
|
+
after: int | None = None,
|
|
77
|
+
) -> tuple[Sequence[CanMutateEnduringObject], int | None]:
|
|
78
|
+
pass # pragma: no cover
|
|
79
|
+
|
|
80
|
+
@overload
|
|
81
|
+
def get(
|
|
82
|
+
self,
|
|
83
|
+
cb: Selector | Sequence[Selector] | None = None,
|
|
84
|
+
*,
|
|
85
|
+
with_positions: Literal[True],
|
|
86
|
+
after: int | None = None,
|
|
87
|
+
) -> Sequence[tuple[CanMutateEnduringObject, int]]:
|
|
88
|
+
pass # pragma: no cover
|
|
89
|
+
|
|
90
|
+
def get(
|
|
91
|
+
self,
|
|
92
|
+
cb: Selector | Sequence[Selector] | None = None,
|
|
93
|
+
*,
|
|
94
|
+
after: int | None = None,
|
|
95
|
+
with_positions: bool = False,
|
|
96
|
+
with_last_position: bool = False,
|
|
97
|
+
) -> (
|
|
98
|
+
Sequence[tuple[CanMutateEnduringObject, int]]
|
|
99
|
+
| tuple[Sequence[CanMutateEnduringObject], int | None]
|
|
100
|
+
| Sequence[CanMutateEnduringObject]
|
|
101
|
+
):
|
|
102
|
+
query = self._cb_to_dcb_query(cb)
|
|
103
|
+
dcb_sequenced_events, head = self.recorder.read(
|
|
104
|
+
query=query,
|
|
105
|
+
after=after,
|
|
106
|
+
)
|
|
107
|
+
if not (with_positions or with_last_position):
|
|
108
|
+
return tuple(
|
|
109
|
+
self.mapper.to_domain_event(s.event) for s in dcb_sequenced_events
|
|
110
|
+
)
|
|
111
|
+
if with_last_position:
|
|
112
|
+
return (
|
|
113
|
+
tuple(
|
|
114
|
+
[self.mapper.to_domain_event(s.event) for s in dcb_sequenced_events]
|
|
115
|
+
),
|
|
116
|
+
head,
|
|
117
|
+
)
|
|
118
|
+
return tuple(
|
|
119
|
+
(self.mapper.to_domain_event(s.event), s.position)
|
|
120
|
+
for s in dcb_sequenced_events
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _cb_to_dcb_query(
|
|
125
|
+
cb: Selector | Sequence[Selector] | None = None,
|
|
126
|
+
) -> DCBQuery:
|
|
127
|
+
cb = [cb] if isinstance(cb, Selector) else cb or []
|
|
128
|
+
return DCBQuery(
|
|
129
|
+
items=[
|
|
130
|
+
DCBQueryItem(
|
|
131
|
+
types=[get_topic(t) for t in s.types],
|
|
132
|
+
tags=list(s.tags),
|
|
133
|
+
)
|
|
134
|
+
for s in cb
|
|
135
|
+
]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class NotFoundError(Exception):
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class DCBInfrastructureFactory(InfrastructureFactory[TTrackingRecorder], ABC):
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def dcb_event_store(self) -> DCBRecorder:
|
|
146
|
+
pass # pragma: no cover
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from eventsourcing.dcb.api import (
|
|
7
|
+
DCBAppendCondition,
|
|
8
|
+
DCBEvent,
|
|
9
|
+
DCBQuery,
|
|
10
|
+
DCBRecorder,
|
|
11
|
+
DCBSequencedEvent,
|
|
12
|
+
)
|
|
13
|
+
from eventsourcing.dcb.persistence import DCBInfrastructureFactory
|
|
14
|
+
from eventsourcing.persistence import IntegrityError, ProgrammingError
|
|
15
|
+
from eventsourcing.popo import POPOFactory, POPORecorder, POPOTrackingRecorder
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Iterator, Sequence
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InMemoryDCBRecorder(DCBRecorder, POPORecorder):
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.events: list[DCBSequencedEvent] = []
|
|
25
|
+
self.position_sequence = self._position_sequence_generator()
|
|
26
|
+
|
|
27
|
+
def read(
|
|
28
|
+
self,
|
|
29
|
+
query: DCBQuery | None = None,
|
|
30
|
+
*,
|
|
31
|
+
after: int | None = None,
|
|
32
|
+
limit: int | None = None,
|
|
33
|
+
) -> tuple[Sequence[DCBSequencedEvent], int | None]:
|
|
34
|
+
query = query or DCBQuery()
|
|
35
|
+
with self._database_lock:
|
|
36
|
+
events_generator = (
|
|
37
|
+
event
|
|
38
|
+
for event in self.events
|
|
39
|
+
if (after is None or event.position > after)
|
|
40
|
+
and (
|
|
41
|
+
not query.items
|
|
42
|
+
or any(
|
|
43
|
+
(not item.types or event.event.type in item.types)
|
|
44
|
+
and (set(event.event.tags) >= set(item.tags))
|
|
45
|
+
for item in query.items
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
events = []
|
|
51
|
+
for i, event in enumerate(events_generator):
|
|
52
|
+
if limit is not None and i >= limit:
|
|
53
|
+
break
|
|
54
|
+
events.append(deepcopy(event))
|
|
55
|
+
if limit is None:
|
|
56
|
+
head = self.events[-1].position if self.events else None
|
|
57
|
+
else:
|
|
58
|
+
head = events[-1].position if events else None
|
|
59
|
+
return events, head
|
|
60
|
+
|
|
61
|
+
def append(
|
|
62
|
+
self, events: Sequence[DCBEvent], condition: DCBAppendCondition | None = None
|
|
63
|
+
) -> int:
|
|
64
|
+
if len(events) == 0:
|
|
65
|
+
msg = "Should be at least one event. Avoid this elsewhere"
|
|
66
|
+
raise ProgrammingError(msg)
|
|
67
|
+
with self._database_lock:
|
|
68
|
+
if condition is not None:
|
|
69
|
+
matched, head = self.read(
|
|
70
|
+
query=condition.fail_if_events_match,
|
|
71
|
+
after=condition.after,
|
|
72
|
+
limit=1,
|
|
73
|
+
)
|
|
74
|
+
if matched:
|
|
75
|
+
raise IntegrityError(condition)
|
|
76
|
+
self.events.extend(
|
|
77
|
+
DCBSequencedEvent(
|
|
78
|
+
position=next(self.position_sequence),
|
|
79
|
+
event=deepcopy(event),
|
|
80
|
+
)
|
|
81
|
+
for event in events
|
|
82
|
+
)
|
|
83
|
+
return self.events[-1].position
|
|
84
|
+
|
|
85
|
+
def _position_sequence_generator(self) -> Iterator[int]:
|
|
86
|
+
position = 1
|
|
87
|
+
while True:
|
|
88
|
+
yield position
|
|
89
|
+
position += 1
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class InMemoryDCBFactory(POPOFactory, DCBInfrastructureFactory[POPOTrackingRecorder]):
|
|
93
|
+
|
|
94
|
+
def dcb_event_store(self) -> DCBRecorder:
|
|
95
|
+
return InMemoryDCBRecorder()
|