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.

@@ -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()