eventsourcing 9.4.3__py3-none-any.whl → 9.4.5__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/system.py CHANGED
@@ -5,16 +5,10 @@ import threading
5
5
  import traceback
6
6
  from abc import ABC, abstractmethod
7
7
  from collections import defaultdict
8
+ from collections.abc import Sequence
8
9
  from queue import Full, Queue
9
10
  from types import FrameType, ModuleType
10
- from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast
11
-
12
- from eventsourcing.projection import EventSourcedProjection
13
-
14
- if TYPE_CHECKING:
15
- from collections.abc import Iterable, Iterator, Sequence
16
-
17
- from typing_extensions import Self
11
+ from typing import TYPE_CHECKING, Any, ClassVar, Generic, Optional, Union, cast
18
12
 
19
13
  from eventsourcing.application import (
20
14
  Application,
@@ -23,7 +17,11 @@ from eventsourcing.application import (
23
17
  Section,
24
18
  TApplication,
25
19
  )
26
- from eventsourcing.domain import DomainEventProtocol, MutableOrImmutableAggregate
20
+ from eventsourcing.domain import (
21
+ DomainEventProtocol,
22
+ MutableOrImmutableAggregate,
23
+ TAggregateID,
24
+ )
27
25
  from eventsourcing.persistence import (
28
26
  Mapper,
29
27
  Notification,
@@ -31,27 +29,34 @@ from eventsourcing.persistence import (
31
29
  Recording,
32
30
  Tracking,
33
31
  )
32
+ from eventsourcing.projection import EventSourcedProjection
34
33
  from eventsourcing.utils import EnvType, get_topic, resolve_topic
35
34
 
36
- ProcessingJob = tuple[DomainEventProtocol, Tracking]
35
+ if TYPE_CHECKING:
36
+ from collections.abc import Iterable, Iterator
37
+
38
+ from typing_extensions import Self
39
+
40
+
41
+ ProcessingJob = tuple[DomainEventProtocol[TAggregateID], Tracking]
37
42
 
38
43
 
39
- class RecordingEvent:
44
+ class RecordingEvent(Generic[TAggregateID]):
40
45
  def __init__(
41
46
  self,
42
47
  application_name: str,
43
- recordings: list[Recording],
48
+ recordings: list[Recording[TAggregateID]],
44
49
  previous_max_notification_id: int | None,
45
50
  ):
46
51
  self.application_name = application_name
47
- self.recordings = recordings
52
+ self.recordings: list[Recording[TAggregateID]] = recordings
48
53
  self.previous_max_notification_id = previous_max_notification_id
49
54
 
50
55
 
51
- ConvertingJob = Optional[Union[RecordingEvent, list[Notification]]]
56
+ ConvertingJob = Optional[Union[RecordingEvent[TAggregateID], Sequence[Notification]]]
52
57
 
53
58
 
54
- class Follower(EventSourcedProjection):
59
+ class Follower(EventSourcedProjection[TAggregateID]):
55
60
  """Extends the :class:`~eventsourcing.projection.EventSourcedProjection` class
56
61
  by pulling notification objects from its notification log readers, by converting
57
62
  the notification objects to domain events and tracking objects and by processing
@@ -68,7 +73,7 @@ class Follower(EventSourcedProjection):
68
73
  def __init__(self, env: EnvType | None = None) -> None:
69
74
  super().__init__(env)
70
75
  self.readers: dict[str, NotificationLogReader] = {}
71
- self.mappers: dict[str, Mapper] = {}
76
+ self.mappers: dict[str, Mapper[TAggregateID]] = {}
72
77
  self.is_threading_enabled = False
73
78
 
74
79
  def follow(self, name: str, log: NotificationLog) -> None:
@@ -103,7 +108,7 @@ class Follower(EventSourcedProjection):
103
108
  self.process_event(domain_event, tracking)
104
109
 
105
110
  def process_event(
106
- self, domain_event: DomainEventProtocol, tracking: Tracking
111
+ self, domain_event: DomainEventProtocol[TAggregateID], tracking: Tracking
107
112
  ) -> None:
108
113
  with self.processing_lock:
109
114
  super().process_event(domain_event, tracking)
@@ -115,7 +120,7 @@ class Follower(EventSourcedProjection):
115
120
  stop: int | None = None,
116
121
  *,
117
122
  inclusive_of_start: bool = True,
118
- ) -> Iterator[list[Notification]]:
123
+ ) -> Iterator[Sequence[Notification]]:
119
124
  """Pulls batches of unseen :class:`~eventsourcing.persistence.Notification`
120
125
  objects from the notification log reader of the named application.
121
126
  """
@@ -127,15 +132,15 @@ class Follower(EventSourcedProjection):
127
132
  )
128
133
 
129
134
  def filter_received_notifications(
130
- self, notifications: list[Notification]
131
- ) -> list[Notification]:
135
+ self, notifications: Sequence[Notification]
136
+ ) -> Sequence[Notification]:
132
137
  if self.topics:
133
138
  return [n for n in notifications if n.topic in self.topics]
134
139
  return notifications
135
140
 
136
141
  def convert_notifications(
137
142
  self, leader_name: str, notifications: Iterable[Notification]
138
- ) -> list[ProcessingJob]:
143
+ ) -> list[ProcessingJob[TAggregateID]]:
139
144
  """Uses the given :class:`~eventsourcing.persistence.Mapper` to convert
140
145
  each received :class:`~eventsourcing.persistence.Notification`
141
146
  object to an :class:`~eventsourcing.domain.AggregateEvent` object
@@ -144,7 +149,9 @@ class Follower(EventSourcedProjection):
144
149
  mapper = self.mappers[leader_name]
145
150
  processing_jobs = []
146
151
  for notification in notifications:
147
- domain_event: DomainEventProtocol = mapper.to_domain_event(notification)
152
+ domain_event: DomainEventProtocol[TAggregateID] = mapper.to_domain_event(
153
+ notification
154
+ )
148
155
  tracking = Tracking(
149
156
  application_name=leader_name,
150
157
  notification_id=notification.id,
@@ -153,15 +160,17 @@ class Follower(EventSourcedProjection):
153
160
  return processing_jobs
154
161
 
155
162
 
156
- class RecordingEventReceiver(ABC):
163
+ class RecordingEventReceiver(Generic[TAggregateID], ABC):
157
164
  """Abstract base class for objects that may receive recording events."""
158
165
 
159
166
  @abstractmethod
160
- def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
167
+ def receive_recording_event(
168
+ self, new_recording_event: RecordingEvent[TAggregateID]
169
+ ) -> None:
161
170
  """Receives a recording event."""
162
171
 
163
172
 
164
- class Leader(Application):
173
+ class Leader(Application[TAggregateID]):
165
174
  """Extends the :class:`~eventsourcing.application.Application`
166
175
  class by also being responsible for keeping track of
167
176
  followers, and prompting followers when there are new
@@ -171,22 +180,24 @@ class Leader(Application):
171
180
  def __init__(self, env: EnvType | None = None) -> None:
172
181
  super().__init__(env)
173
182
  self.previous_max_notification_id: int | None = None
174
- self.followers: list[RecordingEventReceiver] = []
183
+ self.followers: list[RecordingEventReceiver[TAggregateID]] = []
175
184
 
176
- def lead(self, follower: RecordingEventReceiver) -> None:
185
+ def lead(self, follower: RecordingEventReceiver[TAggregateID]) -> None:
177
186
  """Adds given follower to a list of followers."""
178
187
  self.followers.append(follower)
179
188
 
180
189
  def save(
181
190
  self,
182
- *objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
191
+ *objs: MutableOrImmutableAggregate[TAggregateID]
192
+ | DomainEventProtocol[TAggregateID]
193
+ | None,
183
194
  **kwargs: Any,
184
- ) -> list[Recording]:
195
+ ) -> list[Recording[TAggregateID]]:
185
196
  if self.previous_max_notification_id is None:
186
197
  self.previous_max_notification_id = self.recorder.max_notification_id()
187
198
  return super().save(*objs, **kwargs)
188
199
 
189
- def _notify(self, recordings: list[Recording]) -> None:
200
+ def _notify(self, recordings: list[Recording[TAggregateID]]) -> None:
190
201
  """Calls :func:`receive_recording_event` on each follower
191
202
  whenever new events have just been saved.
192
203
  """
@@ -206,7 +217,7 @@ class Leader(Application):
206
217
  follower.receive_recording_event(recording_event)
207
218
 
208
219
 
209
- class ProcessApplication(Leader, Follower):
220
+ class ProcessApplication(Leader[TAggregateID], Follower[TAggregateID]):
210
221
  """Base class for event processing applications
211
222
  that are both "leaders" and followers".
212
223
  """
@@ -219,7 +230,7 @@ class System:
219
230
 
220
231
  def __init__(
221
232
  self,
222
- pipes: Iterable[Iterable[type[Application]]],
233
+ pipes: Iterable[Iterable[type[Application[Any]]]],
223
234
  ):
224
235
  # Remember the caller frame's module, so that we might identify a topic.
225
236
  caller_frame = cast(FrameType, inspect.currentframe()).f_back
@@ -228,7 +239,7 @@ class System:
228
239
 
229
240
  # Build nodes and edges.
230
241
  self.edges: list[tuple[str, str]] = []
231
- classes: dict[str, type[Application]] = {}
242
+ classes: dict[str, type[Application[Any]]] = {}
232
243
  for pipe in pipes:
233
244
  follower_cls = None
234
245
  for cls in pipe:
@@ -287,12 +298,12 @@ class System:
287
298
  def processors(self) -> list[str]:
288
299
  return [name for name in self.leads if name in self.follows]
289
300
 
290
- def get_app_cls(self, name: str) -> type[Application]:
301
+ def get_app_cls(self, name: str) -> type[Application[Any]]:
291
302
  cls = resolve_topic(self.nodes[name])
292
303
  assert issubclass(cls, Application)
293
304
  return cls
294
305
 
295
- def leader_cls(self, name: str) -> type[Leader]:
306
+ def leader_cls(self, name: str) -> type[Leader[Any]]:
296
307
  cls = self.get_app_cls(name)
297
308
  if issubclass(cls, Leader):
298
309
  return cls
@@ -300,7 +311,7 @@ class System:
300
311
  assert issubclass(cls, Leader)
301
312
  return cls
302
313
 
303
- def follower_cls(self, name: str) -> type[Follower]:
314
+ def follower_cls(self, name: str) -> type[Follower[Any]]:
304
315
  cls = self.get_app_cls(name)
305
316
  assert issubclass(cls, Follower)
306
317
  return cls
@@ -322,7 +333,7 @@ class System:
322
333
  return topic
323
334
 
324
335
 
325
- class Runner(ABC):
336
+ class Runner(Generic[TAggregateID], ABC):
326
337
  """Abstract base class for system runners."""
327
338
 
328
339
  def __init__(self, system: System, env: EnvType | None = None):
@@ -369,14 +380,14 @@ class EventProcessingError(Exception):
369
380
  """Raised when event processing fails."""
370
381
 
371
382
 
372
- class SingleThreadedRunner(Runner, RecordingEventReceiver):
383
+ class SingleThreadedRunner(Runner[TAggregateID], RecordingEventReceiver[TAggregateID]):
373
384
  """Runs a :class:`System` in a single thread."""
374
385
 
375
386
  def __init__(self, system: System, env: EnvType | None = None):
376
387
  """Initialises runner with the given :class:`System`."""
377
388
  super().__init__(system=system, env=env)
378
- self.apps: dict[str, Application] = {}
379
- self._recording_events_received: list[RecordingEvent] = []
389
+ self.apps: dict[str, Application[TAggregateID]] = {}
390
+ self._recording_events_received: list[RecordingEvent[TAggregateID]] = []
380
391
  self._prompted_names_lock = threading.Lock()
381
392
  self._prompted_names: set[str] = set()
382
393
  self._processing_lock = threading.Lock()
@@ -407,19 +418,21 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
407
418
  for edge in self.system.edges:
408
419
  leader_name = edge[0]
409
420
  follower_name = edge[1]
410
- leader = cast("Leader", self.apps[leader_name])
411
- follower = cast("Follower", self.apps[follower_name])
421
+ leader = cast("Leader[Any]", self.apps[leader_name])
422
+ follower = cast(Follower[Any], self.apps[follower_name])
412
423
  assert isinstance(leader, Leader)
413
424
  assert isinstance(follower, Follower)
414
425
  follower.follow(leader_name, leader.notification_log)
415
426
 
416
427
  # Setup leaders to lead this runner.
417
428
  for name in self.system.leaders:
418
- leader = cast("Leader", self.apps[name])
429
+ leader = cast("Leader[Any]", self.apps[name])
419
430
  assert isinstance(leader, Leader)
420
431
  leader.lead(self)
421
432
 
422
- def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
433
+ def receive_recording_event(
434
+ self, new_recording_event: RecordingEvent[TAggregateID]
435
+ ) -> None:
423
436
  """Receives recording event by appending the name of the leader
424
437
  to a list of prompted names.
425
438
 
@@ -446,7 +459,7 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
446
459
 
447
460
  for leader_name in prompted_names:
448
461
  for follower_name in self.system.leads[leader_name]:
449
- follower = cast("Follower", self.apps[follower_name])
462
+ follower = cast(Follower[Any], self.apps[follower_name])
450
463
  follower.pull_and_process(leader_name)
451
464
 
452
465
  finally:
@@ -463,14 +476,16 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
463
476
  return app
464
477
 
465
478
 
466
- class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
479
+ class NewSingleThreadedRunner(
480
+ Runner[TAggregateID], RecordingEventReceiver[TAggregateID]
481
+ ):
467
482
  """Runs a :class:`System` in a single thread."""
468
483
 
469
484
  def __init__(self, system: System, env: EnvType | None = None):
470
485
  """Initialises runner with the given :class:`System`."""
471
486
  super().__init__(system=system, env=env)
472
- self.apps: dict[str, Application] = {}
473
- self._recording_events_received: list[RecordingEvent] = []
487
+ self.apps: dict[str, Application[Any]] = {}
488
+ self._recording_events_received: list[RecordingEvent[TAggregateID]] = []
474
489
  self._recording_events_received_lock = threading.Lock()
475
490
  self._processing_lock = threading.Lock()
476
491
  self._previous_max_notification_ids: dict[str, int] = {}
@@ -504,19 +519,21 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
504
519
  for edge in self.system.edges:
505
520
  leader_name = edge[0]
506
521
  follower_name = edge[1]
507
- leader = cast("Leader", self.apps[leader_name])
508
- follower = cast("Follower", self.apps[follower_name])
522
+ leader = cast("Leader[Any]", self.apps[leader_name])
523
+ follower = cast(Follower[Any], self.apps[follower_name])
509
524
  assert isinstance(leader, Leader)
510
525
  assert isinstance(follower, Follower)
511
526
  follower.follow(leader_name, leader.notification_log)
512
527
 
513
528
  # Setup leaders to notify followers.
514
529
  for name in self.system.leaders:
515
- leader = cast("Leader", self.apps[name])
530
+ leader = cast("Leader[Any]", self.apps[name])
516
531
  assert isinstance(leader, Leader)
517
532
  leader.lead(self)
518
533
 
519
- def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
534
+ def receive_recording_event(
535
+ self, new_recording_event: RecordingEvent[TAggregateID]
536
+ ) -> None:
520
537
  """Receives recording event by appending it to list of received recording
521
538
  events.
522
539
 
@@ -603,7 +620,7 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
603
620
  return app
604
621
 
605
622
 
606
- class MultiThreadedRunner(Runner):
623
+ class MultiThreadedRunner(Runner[TAggregateID]):
607
624
  """Runs a :class:`System` with one :class:`MultiThreadedRunnerThread`
608
625
  for each :class:`Follower` in the system definition.
609
626
  """
@@ -611,8 +628,8 @@ class MultiThreadedRunner(Runner):
611
628
  def __init__(self, system: System, env: EnvType | None = None):
612
629
  """Initialises runner with the given :class:`System`."""
613
630
  super().__init__(system=system, env=env)
614
- self.apps: dict[str, Application] = {}
615
- self.threads: dict[str, MultiThreadedRunnerThread] = {}
631
+ self.apps: dict[str, Application[Any]] = {}
632
+ self.threads: dict[str, MultiThreadedRunnerThread[TAggregateID]] = {}
616
633
  self.has_errored = threading.Event()
617
634
 
618
635
  # Construct followers.
@@ -647,8 +664,9 @@ class MultiThreadedRunner(Runner):
647
664
  super().start()
648
665
 
649
666
  # Construct followers.
667
+ thread: MultiThreadedRunnerThread[TAggregateID]
650
668
  for follower_name in self.system.followers:
651
- follower = cast("Follower", self.apps[follower_name])
669
+ follower = cast(Follower[Any], self.apps[follower_name])
652
670
 
653
671
  thread = MultiThreadedRunnerThread(
654
672
  follower=follower,
@@ -663,8 +681,8 @@ class MultiThreadedRunner(Runner):
663
681
 
664
682
  # Lead and follow.
665
683
  for edge in self.system.edges:
666
- leader = cast("Leader", self.apps[edge[0]])
667
- follower = cast("Follower", self.apps[edge[1]])
684
+ leader = cast("Leader[Any]", self.apps[edge[0]])
685
+ follower = cast(Follower[Any], self.apps[edge[1]])
668
686
  follower.follow(leader.name, leader.notification_log)
669
687
  thread = self.threads[follower.name]
670
688
  leader.lead(thread)
@@ -696,14 +714,14 @@ class MultiThreadedRunner(Runner):
696
714
  return app
697
715
 
698
716
 
699
- class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
717
+ class MultiThreadedRunnerThread(RecordingEventReceiver[TAggregateID], threading.Thread):
700
718
  """Runs one :class:`~eventsourcing.system.Follower` application in
701
719
  a :class:`~eventsourcing.system.MultiThreadedRunner`.
702
720
  """
703
721
 
704
722
  def __init__(
705
723
  self,
706
- follower: Follower,
724
+ follower: Follower[Any],
707
725
  has_errored: threading.Event,
708
726
  ):
709
727
  super().__init__(daemon=True)
@@ -740,7 +758,9 @@ class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
740
758
  self.error.__cause__ = e
741
759
  self.has_errored.set()
742
760
 
743
- def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
761
+ def receive_recording_event(
762
+ self, new_recording_event: RecordingEvent[TAggregateID]
763
+ ) -> None:
744
764
  """Receives prompt by appending name of
745
765
  leader to list of prompted names.
746
766
  """
@@ -755,7 +775,9 @@ class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
755
775
  self.is_prompted.set()
756
776
 
757
777
 
758
- class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
778
+ class NewMultiThreadedRunner(
779
+ Runner[TAggregateID], RecordingEventReceiver[TAggregateID]
780
+ ):
759
781
  """Runs a :class:`System` with multiple threads in a new way."""
760
782
 
761
783
  QUEUE_MAX_SIZE: int = 0
@@ -767,10 +789,16 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
767
789
  ):
768
790
  """Initialises runner with the given :class:`System`."""
769
791
  super().__init__(system=system, env=env)
770
- self.apps: dict[str, Application] = {}
771
- self.pulling_threads: dict[str, list[PullingThread]] = {}
772
- self.processing_queues: dict[str, Queue[list[ProcessingJob] | None]] = {}
773
- self.all_threads: list[PullingThread | ConvertingThread | ProcessingThread] = []
792
+ self.apps: dict[str, Application[TAggregateID]] = {}
793
+ self.pulling_threads: dict[str, list[PullingThread[TAggregateID]]] = {}
794
+ self.processing_queues: dict[
795
+ str, Queue[list[ProcessingJob[TAggregateID]] | None]
796
+ ] = {}
797
+ self.all_threads: list[
798
+ PullingThread[TAggregateID]
799
+ | ConvertingThread[TAggregateID]
800
+ | ProcessingThread[TAggregateID]
801
+ ] = []
774
802
  self.has_errored = threading.Event()
775
803
 
776
804
  # Construct followers.
@@ -807,8 +835,8 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
807
835
 
808
836
  # Start the processing threads.
809
837
  for follower_name in self.system.followers:
810
- follower = cast("Follower", self.apps[follower_name])
811
- processing_queue: Queue[list[ProcessingJob] | None] = Queue(
838
+ follower = cast(Follower[Any], self.apps[follower_name])
839
+ processing_queue: Queue[list[ProcessingJob[TAggregateID]] | None] = Queue(
812
840
  maxsize=self.QUEUE_MAX_SIZE
813
841
  )
814
842
  self.processing_queues[follower_name] = processing_queue
@@ -823,13 +851,15 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
823
851
  for edge in self.system.edges:
824
852
  # Set up follower to pull notifications from leader.
825
853
  leader_name = edge[0]
826
- leader = cast("Leader", self.apps[leader_name])
854
+ leader = cast("Leader[Any]", self.apps[leader_name])
827
855
  follower_name = edge[1]
828
- follower = cast("Follower", self.apps[follower_name])
856
+ follower = cast(Follower[Any], self.apps[follower_name])
829
857
  follower.follow(leader.name, leader.notification_log)
830
858
 
831
859
  # Create converting queue.
832
- converting_queue: Queue[ConvertingJob] = Queue(maxsize=self.QUEUE_MAX_SIZE)
860
+ converting_queue: Queue[ConvertingJob[TAggregateID]] = Queue(
861
+ maxsize=self.QUEUE_MAX_SIZE
862
+ )
833
863
 
834
864
  # Start converting thread.
835
865
  converting_thread = ConvertingThread(
@@ -861,7 +891,7 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
861
891
 
862
892
  # Subscribe for notifications from leaders.
863
893
  for leader_name in self.system.leaders:
864
- leader = cast("Leader", self.apps[leader_name])
894
+ leader = cast("Leader[Any]", self.apps[leader_name])
865
895
  assert isinstance(leader, Leader)
866
896
  leader.lead(self)
867
897
 
@@ -890,29 +920,33 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
890
920
  assert isinstance(app, cls)
891
921
  return app
892
922
 
893
- def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
923
+ def receive_recording_event(
924
+ self, new_recording_event: RecordingEvent[TAggregateID]
925
+ ) -> None:
894
926
  for pulling_thread in self.pulling_threads[
895
927
  new_recording_event.application_name
896
928
  ]:
897
929
  pulling_thread.receive_recording_event(new_recording_event)
898
930
 
899
931
 
900
- class PullingThread(threading.Thread):
932
+ class PullingThread(threading.Thread, Generic[TAggregateID]):
901
933
  """Receives or pulls notifications from the given leader, and
902
934
  puts them on a queue for conversion into processing jobs.
903
935
  """
904
936
 
905
937
  def __init__(
906
938
  self,
907
- converting_queue: Queue[ConvertingJob],
908
- follower: Follower,
939
+ converting_queue: Queue[ConvertingJob[TAggregateID]],
940
+ follower: Follower[Any],
909
941
  leader_name: str,
910
942
  has_errored: threading.Event,
911
943
  ):
912
944
  super().__init__(daemon=True)
913
945
  self.overflow_event = threading.Event()
914
- self.recording_event_queue: Queue[RecordingEvent | None] = Queue(maxsize=100)
915
- self.converting_queue = converting_queue
946
+ self.recording_event_queue: Queue[RecordingEvent[TAggregateID] | None] = Queue(
947
+ maxsize=100
948
+ )
949
+ self.converting_queue: Queue[ConvertingJob[TAggregateID]] = converting_queue
916
950
  self.receive_lock = threading.Lock()
917
951
  self.follower = follower
918
952
  self.leader_name = leader_name
@@ -968,7 +1002,9 @@ class PullingThread(threading.Thread):
968
1002
  self.error.__cause__ = e
969
1003
  self.has_errored.set()
970
1004
 
971
- def receive_recording_event(self, recording_event: RecordingEvent) -> None:
1005
+ def receive_recording_event(
1006
+ self, recording_event: RecordingEvent[TAggregateID]
1007
+ ) -> None:
972
1008
  try:
973
1009
  self.recording_event_queue.put(recording_event, timeout=0)
974
1010
  except Full:
@@ -979,20 +1015,22 @@ class PullingThread(threading.Thread):
979
1015
  self.recording_event_queue.put(None)
980
1016
 
981
1017
 
982
- class ConvertingThread(threading.Thread):
1018
+ class ConvertingThread(threading.Thread, Generic[TAggregateID]):
983
1019
  """Converts notifications into processing jobs."""
984
1020
 
985
1021
  def __init__(
986
1022
  self,
987
- converting_queue: Queue[ConvertingJob],
988
- processing_queue: Queue[list[ProcessingJob] | None],
989
- follower: Follower,
1023
+ converting_queue: Queue[ConvertingJob[TAggregateID]],
1024
+ processing_queue: Queue[list[ProcessingJob[TAggregateID]] | None],
1025
+ follower: Follower[Any],
990
1026
  leader_name: str,
991
1027
  has_errored: threading.Event,
992
1028
  ):
993
1029
  super().__init__(daemon=True)
994
- self.converting_queue = converting_queue
995
- self.processing_queue = processing_queue
1030
+ self.converting_queue: Queue[ConvertingJob[TAggregateID]] = converting_queue
1031
+ self.processing_queue: Queue[list[ProcessingJob[TAggregateID]] | None] = (
1032
+ processing_queue
1033
+ )
996
1034
  self.follower = follower
997
1035
  self.leader_name = leader_name
998
1036
  self.error: Exception | None = None
@@ -1046,19 +1084,21 @@ class ConvertingThread(threading.Thread):
1046
1084
  self.converting_queue.put(None)
1047
1085
 
1048
1086
 
1049
- class ProcessingThread(threading.Thread):
1087
+ class ProcessingThread(threading.Thread, Generic[TAggregateID]):
1050
1088
  """A processing thread gets events from a processing queue, and
1051
1089
  calls the application's process_event() method.
1052
1090
  """
1053
1091
 
1054
1092
  def __init__(
1055
1093
  self,
1056
- processing_queue: Queue[list[ProcessingJob] | None],
1057
- follower: Follower,
1094
+ processing_queue: Queue[list[ProcessingJob[TAggregateID]] | None],
1095
+ follower: Follower[Any],
1058
1096
  has_errored: threading.Event,
1059
1097
  ):
1060
1098
  super().__init__(daemon=True)
1061
- self.processing_queue = processing_queue
1099
+ self.processing_queue: Queue[list[ProcessingJob[TAggregateID]] | None] = (
1100
+ processing_queue
1101
+ )
1062
1102
  self.follower = follower
1063
1103
  self.error: Exception | None = None
1064
1104
  self.has_errored = has_errored
@@ -1132,7 +1172,7 @@ class NotificationLogReader:
1132
1172
  stop: int | None = None,
1133
1173
  topics: Sequence[str] = (),
1134
1174
  inclusive_of_start: bool = True,
1135
- ) -> Iterator[list[Notification]]:
1175
+ ) -> Iterator[Sequence[Notification]]:
1136
1176
  """Returns a generator that yields lists of event notifications
1137
1177
  from the reader's notification log, starting from given start
1138
1178
  position (a notification ID).