eventsourcing 9.3.4__py3-none-any.whl → 9.4.0a1__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,30 +1,38 @@
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, Dict, Iterable, List, Sequence, Set, Type
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,
15
17
  Tracking,
18
+ TrackingRecorder,
16
19
  )
17
- from eventsourcing.utils import reversed_keys
20
+ from eventsourcing.utils import resolve_topic, reversed_keys
18
21
 
19
- if TYPE_CHECKING: # pragma: nocover
22
+ if TYPE_CHECKING: # pragma: no cover
20
23
  from uuid import UUID
21
24
 
22
25
 
23
- class POPOAggregateRecorder(AggregateRecorder):
26
+ class POPORecorder:
24
27
  def __init__(self) -> None:
28
+ self._database_lock = Lock()
29
+
30
+
31
+ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
32
+ def __init__(self) -> None:
33
+ super().__init__()
25
34
  self._stored_events: List[StoredEvent] = []
26
35
  self._stored_events_index: Dict[UUID, Dict[int, int]] = defaultdict(dict)
27
- self._database_lock = Lock()
28
36
 
29
37
  def insert_events(
30
38
  self, stored_events: List[StoredEvent], **kwargs: Any
@@ -91,23 +99,36 @@ class POPOAggregateRecorder(AggregateRecorder):
91
99
  return results
92
100
 
93
101
 
94
- class POPOApplicationRecorder(ApplicationRecorder, POPOAggregateRecorder):
102
+ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
103
+ def __init__(self) -> None:
104
+ super().__init__()
105
+ self._listeners: Set[Event] = set()
106
+
95
107
  def insert_events(
96
108
  self, stored_events: List[StoredEvent], **kwargs: Any
97
109
  ) -> Sequence[int] | None:
98
- return self._insert_events(stored_events, **kwargs)
110
+ notification_ids = self._insert_events(stored_events, **kwargs)
111
+ self._notify_listeners()
112
+ return notification_ids
99
113
 
100
114
  def select_notifications(
101
115
  self,
102
- start: int,
116
+ start: int | None,
103
117
  limit: int,
104
118
  stop: int | None = None,
105
119
  topics: Sequence[str] = (),
120
+ *,
121
+ inclusive_of_start: bool = True,
106
122
  ) -> List[Notification]:
107
123
  with self._database_lock:
108
124
  results = []
125
+ if start is None:
126
+ start = 1
127
+ inclusive_of_start = True
128
+ if not inclusive_of_start:
129
+ start += 1
109
130
  start = max(start, 1) # Don't use negative indexes!
110
- i = start - 1
131
+ i = start - 1 # Zero-based indexing.
111
132
  while True:
112
133
  if stop is not None and i > stop - 1:
113
134
  break
@@ -130,28 +151,82 @@ class POPOApplicationRecorder(ApplicationRecorder, POPOAggregateRecorder):
130
151
  break
131
152
  return results
132
153
 
133
- def max_notification_id(self) -> int:
154
+ def max_notification_id(self) -> int | None:
134
155
  with self._database_lock:
135
- return len(self._stored_events)
156
+ return len(self._stored_events) or None
157
+
158
+ def subscribe(self, gt: int | None = None) -> POPOSubscription:
159
+ return POPOSubscription(self, gt)
136
160
 
161
+ def listen(self, event: Event) -> None:
162
+ self._listeners.add(event)
137
163
 
138
- class POPOProcessRecorder(ProcessRecorder, POPOApplicationRecorder):
164
+ def unlisten(self, event: Event) -> None:
165
+ with contextlib.suppress(KeyError):
166
+ self._listeners.remove(event)
167
+
168
+ def _notify_listeners(self) -> None:
169
+ for listener in self._listeners:
170
+ listener.set()
171
+
172
+
173
+ class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
174
+ def __init__(
175
+ self, recorder: POPOApplicationRecorder, gt: int | None = None
176
+ ) -> None:
177
+ assert isinstance(recorder, POPOApplicationRecorder)
178
+ super().__init__(recorder=recorder, gt=gt)
179
+ self._recorder.listen(self._has_been_notified)
180
+
181
+ def stop(self) -> None:
182
+ super().stop()
183
+ self._recorder.unlisten(self._has_been_notified)
184
+
185
+
186
+ class POPOTrackingRecorder(POPORecorder, TrackingRecorder):
139
187
  def __init__(self) -> None:
140
188
  super().__init__()
141
189
  self._tracking_table: Dict[str, set[int]] = defaultdict(set)
142
- self._max_tracking_ids: Dict[str, int] = defaultdict(lambda: 0)
190
+ self._max_tracking_ids: Dict[str, int | None] = defaultdict(lambda: None)
191
+
192
+ def _assert_tracking_uniqueness(self, tracking: Tracking) -> None:
193
+ if tracking.notification_id in self._tracking_table[tracking.application_name]:
194
+ msg = (
195
+ f"Already recorded notification ID {tracking.notification_id} "
196
+ f"for application {tracking.application_name}"
197
+ )
198
+ raise IntegrityError(msg)
199
+
200
+ def insert_tracking(self, tracking: Tracking) -> None:
201
+ with self._database_lock:
202
+ self._assert_tracking_uniqueness(tracking)
203
+ self._insert_tracking(tracking)
204
+
205
+ def _insert_tracking(self, tracking: Tracking) -> None:
206
+ self._tracking_table[tracking.application_name].add(tracking.notification_id)
207
+ max_tracking_id = self._max_tracking_ids[tracking.application_name]
208
+ if max_tracking_id is None or max_tracking_id < tracking.notification_id:
209
+ self._max_tracking_ids[tracking.application_name] = tracking.notification_id
210
+
211
+ def max_tracking_id(self, application_name: str) -> int | None:
212
+ with self._database_lock:
213
+ return self._max_tracking_ids[application_name]
214
+
215
+ def has_tracking_id(self, application_name: str, notification_id: int) -> bool:
216
+ with self._database_lock:
217
+ return notification_id in self._tracking_table[application_name]
143
218
 
219
+
220
+ class POPOProcessRecorder(
221
+ POPOTrackingRecorder, POPOApplicationRecorder, ProcessRecorder
222
+ ):
144
223
  def _assert_uniqueness(
145
224
  self, stored_events: List[StoredEvent], **kwargs: Any
146
225
  ) -> None:
147
226
  super()._assert_uniqueness(stored_events, **kwargs)
148
227
  t: Tracking | None = kwargs.get("tracking", None)
149
- if t and t.notification_id in self._tracking_table[t.application_name]:
150
- msg = (
151
- f"Already recorded notification ID {t.notification_id} "
152
- f"for application {t.application_name}"
153
- )
154
- raise IntegrityError(msg)
228
+ if t:
229
+ self._assert_tracking_uniqueness(t)
155
230
 
156
231
  def _update_table(
157
232
  self, stored_events: List[StoredEvent], **kwargs: Any
@@ -159,26 +234,32 @@ class POPOProcessRecorder(ProcessRecorder, POPOApplicationRecorder):
159
234
  notification_ids = super()._update_table(stored_events, **kwargs)
160
235
  t: Tracking | None = kwargs.get("tracking", None)
161
236
  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
237
+ self._insert_tracking(t)
165
238
  return notification_ids
166
239
 
167
- def max_tracking_id(self, application_name: str) -> int:
168
- with self._database_lock:
169
- return self._max_tracking_ids[application_name]
170
-
171
- def has_tracking_id(self, application_name: str, notification_id: int) -> bool:
172
- with self._database_lock:
173
- return notification_id in self._tracking_table[application_name]
174
-
175
240
 
176
- class Factory(InfrastructureFactory):
241
+ class POPOFactory(InfrastructureFactory[POPOTrackingRecorder]):
177
242
  def aggregate_recorder(self, purpose: str = "events") -> AggregateRecorder:
178
243
  return POPOAggregateRecorder()
179
244
 
180
245
  def application_recorder(self) -> ApplicationRecorder:
181
246
  return POPOApplicationRecorder()
182
247
 
248
+ def tracking_recorder(
249
+ self, tracking_recorder_class: Type[POPOTrackingRecorder] | None = None
250
+ ) -> POPOTrackingRecorder:
251
+ if tracking_recorder_class is None:
252
+ tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
253
+ if tracking_recorder_topic:
254
+ tracking_recorder_class = resolve_topic(tracking_recorder_topic)
255
+ else:
256
+ tracking_recorder_class = POPOTrackingRecorder
257
+ assert tracking_recorder_class is not None
258
+ assert issubclass(tracking_recorder_class, POPOTrackingRecorder)
259
+ return tracking_recorder_class()
260
+
183
261
  def process_recorder(self) -> ProcessRecorder:
184
262
  return POPOProcessRecorder()
263
+
264
+
265
+ Factory = POPOFactory