eventsourcing 9.3.5__py3-none-any.whl → 9.4.0__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/popo.py CHANGED
@@ -1,45 +1,55 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  from collections import defaultdict
4
- from threading import Lock
5
- from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence
5
+ from threading import Event, Lock
6
+ from typing import TYPE_CHECKING, Any
6
7
 
7
8
  from eventsourcing.persistence import (
8
9
  AggregateRecorder,
9
10
  ApplicationRecorder,
10
11
  InfrastructureFactory,
11
12
  IntegrityError,
13
+ ListenNotifySubscription,
12
14
  Notification,
13
15
  ProcessRecorder,
14
16
  StoredEvent,
17
+ Subscription,
15
18
  Tracking,
19
+ TrackingRecorder,
16
20
  )
17
- from eventsourcing.utils import reversed_keys
21
+ from eventsourcing.utils import resolve_topic, reversed_keys
18
22
 
19
- if TYPE_CHECKING: # pragma: nocover
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Iterable, Sequence
20
25
  from uuid import UUID
21
26
 
22
27
 
23
- class POPOAggregateRecorder(AggregateRecorder):
28
+ class POPORecorder:
24
29
  def __init__(self) -> None:
25
- self._stored_events: List[StoredEvent] = []
26
- self._stored_events_index: Dict[UUID, Dict[int, int]] = defaultdict(dict)
27
30
  self._database_lock = Lock()
28
31
 
32
+
33
+ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
34
+ def __init__(self) -> None:
35
+ super().__init__()
36
+ self._stored_events: list[StoredEvent] = []
37
+ self._stored_events_index: dict[UUID, dict[int, int]] = defaultdict(dict)
38
+
29
39
  def insert_events(
30
- self, stored_events: List[StoredEvent], **kwargs: Any
40
+ self, stored_events: list[StoredEvent], **kwargs: Any
31
41
  ) -> Sequence[int] | None:
32
42
  self._insert_events(stored_events, **kwargs)
33
43
  return None
34
44
 
35
45
  def _insert_events(
36
- self, stored_events: List[StoredEvent], **kwargs: Any
46
+ self, stored_events: list[StoredEvent], **kwargs: Any
37
47
  ) -> Sequence[int] | None:
38
48
  with self._database_lock:
39
49
  self._assert_uniqueness(stored_events, **kwargs)
40
50
  return self._update_table(stored_events, **kwargs)
41
51
 
42
- def _assert_uniqueness(self, stored_events: List[StoredEvent], **_: Any) -> None:
52
+ def _assert_uniqueness(self, stored_events: list[StoredEvent], **_: Any) -> None:
43
53
  new = set()
44
54
  for s in stored_events:
45
55
  # Check events don't already exist.
@@ -53,7 +63,7 @@ class POPOAggregateRecorder(AggregateRecorder):
53
63
  raise IntegrityError(msg)
54
64
 
55
65
  def _update_table(
56
- self, stored_events: List[StoredEvent], **_: Any
66
+ self, stored_events: list[StoredEvent], **_: Any
57
67
  ) -> Sequence[int] | None:
58
68
  notification_ids = []
59
69
  for s in stored_events:
@@ -72,7 +82,7 @@ class POPOAggregateRecorder(AggregateRecorder):
72
82
  lte: int | None = None,
73
83
  desc: bool = False,
74
84
  limit: int | None = None,
75
- ) -> List[StoredEvent]:
85
+ ) -> list[StoredEvent]:
76
86
  with self._database_lock:
77
87
  results = []
78
88
 
@@ -91,23 +101,36 @@ class POPOAggregateRecorder(AggregateRecorder):
91
101
  return results
92
102
 
93
103
 
94
- class POPOApplicationRecorder(ApplicationRecorder, POPOAggregateRecorder):
104
+ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
105
+ def __init__(self) -> None:
106
+ super().__init__()
107
+ self._listeners: set[Event] = set()
108
+
95
109
  def insert_events(
96
- self, stored_events: List[StoredEvent], **kwargs: Any
110
+ self, stored_events: list[StoredEvent], **kwargs: Any
97
111
  ) -> Sequence[int] | None:
98
- return self._insert_events(stored_events, **kwargs)
112
+ notification_ids = self._insert_events(stored_events, **kwargs)
113
+ self._notify_listeners()
114
+ return notification_ids
99
115
 
100
116
  def select_notifications(
101
117
  self,
102
- start: int,
118
+ start: int | None,
103
119
  limit: int,
104
120
  stop: int | None = None,
105
121
  topics: Sequence[str] = (),
106
- ) -> List[Notification]:
122
+ *,
123
+ inclusive_of_start: bool = True,
124
+ ) -> list[Notification]:
107
125
  with self._database_lock:
108
126
  results = []
127
+ if start is None:
128
+ start = 1
129
+ inclusive_of_start = True
130
+ if not inclusive_of_start:
131
+ start += 1
109
132
  start = max(start, 1) # Don't use negative indexes!
110
- i = start - 1
133
+ i = start - 1 # Zero-based indexing.
111
134
  while True:
112
135
  if stop is not None and i > stop - 1:
113
136
  break
@@ -130,55 +153,124 @@ class POPOApplicationRecorder(ApplicationRecorder, POPOAggregateRecorder):
130
153
  break
131
154
  return results
132
155
 
133
- def max_notification_id(self) -> int:
156
+ def max_notification_id(self) -> int | None:
134
157
  with self._database_lock:
135
- return len(self._stored_events)
158
+ return len(self._stored_events) or None
159
+
160
+ def subscribe(
161
+ self, gt: int | None = None, topics: Sequence[str] = ()
162
+ ) -> Subscription[ApplicationRecorder]:
163
+ return POPOSubscription(recorder=self, gt=gt, topics=topics)
164
+
165
+ def listen(self, event: Event) -> None:
166
+ self._listeners.add(event)
167
+
168
+ def unlisten(self, event: Event) -> None:
169
+ with contextlib.suppress(KeyError):
170
+ self._listeners.remove(event)
136
171
 
172
+ def _notify_listeners(self) -> None:
173
+ for listener in self._listeners:
174
+ listener.set()
137
175
 
138
- class POPOProcessRecorder(ProcessRecorder, POPOApplicationRecorder):
176
+
177
+ class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
178
+ def __init__(
179
+ self,
180
+ recorder: POPOApplicationRecorder,
181
+ gt: int | None = None,
182
+ topics: Sequence[str] = (),
183
+ ) -> None:
184
+ assert isinstance(recorder, POPOApplicationRecorder)
185
+ super().__init__(recorder=recorder, gt=gt, topics=topics)
186
+ self._recorder.listen(self._has_been_notified)
187
+
188
+ def stop(self) -> None:
189
+ super().stop()
190
+ self._recorder.unlisten(self._has_been_notified)
191
+
192
+
193
+ class POPOTrackingRecorder(POPORecorder, TrackingRecorder):
139
194
  def __init__(self) -> None:
140
195
  super().__init__()
141
- self._tracking_table: Dict[str, set[int]] = defaultdict(set)
142
- self._max_tracking_ids: Dict[str, int] = defaultdict(lambda: 0)
196
+ self._tracking_table: dict[str, set[int]] = defaultdict(set)
197
+ self._max_tracking_ids: dict[str, int | None] = defaultdict(lambda: None)
143
198
 
144
- def _assert_uniqueness(
145
- self, stored_events: List[StoredEvent], **kwargs: Any
146
- ) -> None:
147
- super()._assert_uniqueness(stored_events, **kwargs)
148
- t: Tracking | None = kwargs.get("tracking", None)
149
- if t and t.notification_id in self._tracking_table[t.application_name]:
199
+ def _assert_tracking_uniqueness(self, tracking: Tracking) -> None:
200
+ if tracking.notification_id in self._tracking_table[tracking.application_name]:
150
201
  msg = (
151
- f"Already recorded notification ID {t.notification_id} "
152
- f"for application {t.application_name}"
202
+ f"Already recorded notification ID {tracking.notification_id} "
203
+ f"for application {tracking.application_name}"
153
204
  )
154
205
  raise IntegrityError(msg)
155
206
 
156
- def _update_table(
157
- self, stored_events: List[StoredEvent], **kwargs: Any
158
- ) -> Sequence[int] | None:
159
- notification_ids = super()._update_table(stored_events, **kwargs)
160
- t: Tracking | None = kwargs.get("tracking", None)
161
- if t:
162
- self._tracking_table[t.application_name].add(t.notification_id)
163
- if self._max_tracking_ids[t.application_name] < t.notification_id:
164
- self._max_tracking_ids[t.application_name] = t.notification_id
165
- return notification_ids
207
+ def insert_tracking(self, tracking: Tracking) -> None:
208
+ with self._database_lock:
209
+ self._assert_tracking_uniqueness(tracking)
210
+ self._insert_tracking(tracking)
166
211
 
167
- def max_tracking_id(self, application_name: str) -> int:
212
+ def _insert_tracking(self, tracking: Tracking) -> None:
213
+ self._tracking_table[tracking.application_name].add(tracking.notification_id)
214
+ max_tracking_id = self._max_tracking_ids[tracking.application_name]
215
+ if max_tracking_id is None or max_tracking_id < tracking.notification_id:
216
+ self._max_tracking_ids[tracking.application_name] = tracking.notification_id
217
+
218
+ def max_tracking_id(self, application_name: str) -> int | None:
168
219
  with self._database_lock:
169
220
  return self._max_tracking_ids[application_name]
170
221
 
171
- def has_tracking_id(self, application_name: str, notification_id: int) -> bool:
222
+ def has_tracking_id(
223
+ self, application_name: str, notification_id: int | None
224
+ ) -> bool:
225
+ if notification_id is None:
226
+ return True
172
227
  with self._database_lock:
173
228
  return notification_id in self._tracking_table[application_name]
174
229
 
175
230
 
176
- class Factory(InfrastructureFactory):
231
+ class POPOProcessRecorder(
232
+ POPOTrackingRecorder, POPOApplicationRecorder, ProcessRecorder
233
+ ):
234
+ def _assert_uniqueness(
235
+ self, stored_events: list[StoredEvent], **kwargs: Any
236
+ ) -> None:
237
+ super()._assert_uniqueness(stored_events, **kwargs)
238
+ t: Tracking | None = kwargs.get("tracking")
239
+ if t:
240
+ self._assert_tracking_uniqueness(t)
241
+
242
+ def _update_table(
243
+ self, stored_events: list[StoredEvent], **kwargs: Any
244
+ ) -> Sequence[int] | None:
245
+ notification_ids = super()._update_table(stored_events, **kwargs)
246
+ t: Tracking | None = kwargs.get("tracking")
247
+ if t:
248
+ self._insert_tracking(t)
249
+ return notification_ids
250
+
251
+
252
+ class POPOFactory(InfrastructureFactory[POPOTrackingRecorder]):
177
253
  def aggregate_recorder(self, purpose: str = "events") -> AggregateRecorder:
178
254
  return POPOAggregateRecorder()
179
255
 
180
256
  def application_recorder(self) -> ApplicationRecorder:
181
257
  return POPOApplicationRecorder()
182
258
 
259
+ def tracking_recorder(
260
+ self, tracking_recorder_class: type[POPOTrackingRecorder] | None = None
261
+ ) -> POPOTrackingRecorder:
262
+ if tracking_recorder_class is None:
263
+ tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
264
+ if tracking_recorder_topic:
265
+ tracking_recorder_class = resolve_topic(tracking_recorder_topic)
266
+ else:
267
+ tracking_recorder_class = POPOTrackingRecorder
268
+ assert tracking_recorder_class is not None
269
+ assert issubclass(tracking_recorder_class, POPOTrackingRecorder)
270
+ return tracking_recorder_class()
271
+
183
272
  def process_recorder(self) -> ProcessRecorder:
184
273
  return POPOProcessRecorder()
274
+
275
+
276
+ Factory = POPOFactory