eventsourcing 9.4.0b1__py3-none-any.whl → 9.4.0b3__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 +53 -104
- eventsourcing/cipher.py +3 -8
- eventsourcing/compressor.py +2 -6
- eventsourcing/cryptography.py +3 -8
- eventsourcing/dispatch.py +2 -2
- eventsourcing/domain.py +211 -291
- eventsourcing/interface.py +10 -24
- eventsourcing/persistence.py +122 -219
- eventsourcing/popo.py +2 -2
- eventsourcing/postgres.py +7 -10
- eventsourcing/projection.py +81 -54
- eventsourcing/sqlite.py +4 -7
- eventsourcing/system.py +89 -156
- eventsourcing/tests/application.py +137 -74
- eventsourcing/tests/domain.py +14 -34
- eventsourcing/tests/persistence.py +21 -18
- eventsourcing/utils.py +11 -17
- {eventsourcing-9.4.0b1.dist-info → eventsourcing-9.4.0b3.dist-info}/METADATA +1 -1
- eventsourcing-9.4.0b3.dist-info/RECORD +26 -0
- eventsourcing-9.4.0b1.dist-info/RECORD +0 -26
- {eventsourcing-9.4.0b1.dist-info → eventsourcing-9.4.0b3.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.4.0b1.dist-info → eventsourcing-9.4.0b3.dist-info}/LICENSE +0 -0
- {eventsourcing-9.4.0b1.dist-info → eventsourcing-9.4.0b3.dist-info}/WHEEL +0 -0
eventsourcing/system.py
CHANGED
|
@@ -6,11 +6,12 @@ import traceback
|
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from queue import Full, Queue
|
|
9
|
-
from types import FrameType, ModuleType
|
|
10
9
|
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
12
|
from collections.abc import Iterable, Iterator, Sequence
|
|
13
|
+
from types import FrameType, ModuleType
|
|
14
|
+
|
|
14
15
|
from typing_extensions import Self
|
|
15
16
|
|
|
16
17
|
from eventsourcing.application import (
|
|
@@ -52,8 +53,7 @@ ConvertingJob = Optional[Union[RecordingEvent, list[Notification]]]
|
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
class Follower(Application):
|
|
55
|
-
"""
|
|
56
|
-
Extends the :class:`~eventsourcing.application.Application` class
|
|
56
|
+
"""Extends the :class:`~eventsourcing.application.Application` class
|
|
57
57
|
by using a process recorder as its application recorder, by keeping
|
|
58
58
|
track of the applications it is following, and pulling and processing
|
|
59
59
|
new domain event notifications through its :func:`policy` method.
|
|
@@ -71,15 +71,13 @@ class Follower(Application):
|
|
|
71
71
|
self.processing_lock = threading.Lock()
|
|
72
72
|
|
|
73
73
|
def construct_recorder(self) -> ProcessRecorder:
|
|
74
|
-
"""
|
|
75
|
-
Constructs and returns a :class:`~eventsourcing.persistence.ProcessRecorder`
|
|
74
|
+
"""Constructs and returns a :class:`~eventsourcing.persistence.ProcessRecorder`
|
|
76
75
|
for the application to use as its application recorder.
|
|
77
76
|
"""
|
|
78
77
|
return self.factory.process_recorder()
|
|
79
78
|
|
|
80
79
|
def follow(self, name: str, log: NotificationLog) -> None:
|
|
81
|
-
"""
|
|
82
|
-
Constructs a notification log reader and a mapper for
|
|
80
|
+
"""Constructs a notification log reader and a mapper for
|
|
83
81
|
the named application, and adds them to its collections
|
|
84
82
|
of readers and mappers.
|
|
85
83
|
"""
|
|
@@ -97,9 +95,7 @@ class Follower(Application):
|
|
|
97
95
|
def pull_and_process(
|
|
98
96
|
self, leader_name: str, start: int | None = None, stop: int | None = None
|
|
99
97
|
) -> None:
|
|
100
|
-
"""
|
|
101
|
-
Pull and process new domain event notifications.
|
|
102
|
-
"""
|
|
98
|
+
"""Pull and process new domain event notifications."""
|
|
103
99
|
if start is None:
|
|
104
100
|
start = self.recorder.max_tracking_id(leader_name)
|
|
105
101
|
for notifications in self.pull_notifications(
|
|
@@ -119,8 +115,7 @@ class Follower(Application):
|
|
|
119
115
|
*,
|
|
120
116
|
inclusive_of_start: bool = True,
|
|
121
117
|
) -> Iterator[list[Notification]]:
|
|
122
|
-
"""
|
|
123
|
-
Pulls batches of unseen :class:`~eventsourcing.persistence.Notification`
|
|
118
|
+
"""Pulls batches of unseen :class:`~eventsourcing.persistence.Notification`
|
|
124
119
|
objects from the notification log reader of the named application.
|
|
125
120
|
"""
|
|
126
121
|
return self.readers[leader_name].select(
|
|
@@ -140,8 +135,7 @@ class Follower(Application):
|
|
|
140
135
|
def convert_notifications(
|
|
141
136
|
self, leader_name: str, notifications: Iterable[Notification]
|
|
142
137
|
) -> list[ProcessingJob]:
|
|
143
|
-
"""
|
|
144
|
-
Uses the given :class:`~eventsourcing.persistence.Mapper` to convert
|
|
138
|
+
"""Uses the given :class:`~eventsourcing.persistence.Mapper` to convert
|
|
145
139
|
each received :class:`~eventsourcing.persistence.Notification`
|
|
146
140
|
object to an :class:`~eventsourcing.domain.AggregateEvent` object
|
|
147
141
|
paired with a :class:`~eventsourcing.persistence.Tracking` object.
|
|
@@ -161,8 +155,7 @@ class Follower(Application):
|
|
|
161
155
|
def process_event(
|
|
162
156
|
self, domain_event: DomainEventProtocol, tracking: Tracking
|
|
163
157
|
) -> None:
|
|
164
|
-
"""
|
|
165
|
-
Calls :func:`~eventsourcing.system.Follower.policy` method with
|
|
158
|
+
"""Calls :func:`~eventsourcing.system.Follower.policy` method with
|
|
166
159
|
the given :class:`~eventsourcing.domain.AggregateEvent` and a
|
|
167
160
|
new :class:`~eventsourcing.application.ProcessingEvent` created from
|
|
168
161
|
the given :class:`~eventsourcing.persistence.Tracking` object.
|
|
@@ -204,8 +197,7 @@ class Follower(Application):
|
|
|
204
197
|
domain_event: DomainEventProtocol,
|
|
205
198
|
processing_event: ProcessingEvent,
|
|
206
199
|
) -> None:
|
|
207
|
-
"""
|
|
208
|
-
Abstract domain event processing policy method. Must be
|
|
200
|
+
"""Abstract domain event processing policy method. Must be
|
|
209
201
|
implemented by event processing applications. When
|
|
210
202
|
processing the given domain event, event processing
|
|
211
203
|
applications must use the :func:`~ProcessingEvent.collect_events`
|
|
@@ -219,20 +211,15 @@ class Follower(Application):
|
|
|
219
211
|
|
|
220
212
|
|
|
221
213
|
class RecordingEventReceiver(ABC):
|
|
222
|
-
"""
|
|
223
|
-
Abstract base class for objects that may receive recording events.
|
|
224
|
-
"""
|
|
214
|
+
"""Abstract base class for objects that may receive recording events."""
|
|
225
215
|
|
|
226
216
|
@abstractmethod
|
|
227
|
-
def receive_recording_event(self,
|
|
228
|
-
"""
|
|
229
|
-
Receives a recording event.
|
|
230
|
-
"""
|
|
217
|
+
def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
|
|
218
|
+
"""Receives a recording event."""
|
|
231
219
|
|
|
232
220
|
|
|
233
221
|
class Leader(Application):
|
|
234
|
-
"""
|
|
235
|
-
Extends the :class:`~eventsourcing.application.Application`
|
|
222
|
+
"""Extends the :class:`~eventsourcing.application.Application`
|
|
236
223
|
class by also being responsible for keeping track of
|
|
237
224
|
followers, and prompting followers when there are new
|
|
238
225
|
domain event notifications to be pulled and processed.
|
|
@@ -244,9 +231,7 @@ class Leader(Application):
|
|
|
244
231
|
self.followers: list[RecordingEventReceiver] = []
|
|
245
232
|
|
|
246
233
|
def lead(self, follower: RecordingEventReceiver) -> None:
|
|
247
|
-
"""
|
|
248
|
-
Adds given follower to a list of followers.
|
|
249
|
-
"""
|
|
234
|
+
"""Adds given follower to a list of followers."""
|
|
250
235
|
self.followers.append(follower)
|
|
251
236
|
|
|
252
237
|
def save(
|
|
@@ -259,8 +244,7 @@ class Leader(Application):
|
|
|
259
244
|
return super().save(*objs, **kwargs)
|
|
260
245
|
|
|
261
246
|
def _notify(self, recordings: list[Recording]) -> None:
|
|
262
|
-
"""
|
|
263
|
-
Calls :func:`receive_recording_event` on each follower
|
|
247
|
+
"""Calls :func:`receive_recording_event` on each follower
|
|
264
248
|
whenever new events have just been saved.
|
|
265
249
|
"""
|
|
266
250
|
super()._notify(recordings)
|
|
@@ -280,16 +264,13 @@ class Leader(Application):
|
|
|
280
264
|
|
|
281
265
|
|
|
282
266
|
class ProcessApplication(Leader, Follower):
|
|
283
|
-
"""
|
|
284
|
-
Base class for event processing applications
|
|
267
|
+
"""Base class for event processing applications
|
|
285
268
|
that are both "leaders" and followers".
|
|
286
269
|
"""
|
|
287
270
|
|
|
288
271
|
|
|
289
272
|
class System:
|
|
290
|
-
"""
|
|
291
|
-
Defines a system of applications.
|
|
292
|
-
"""
|
|
273
|
+
"""Defines a system of applications."""
|
|
293
274
|
|
|
294
275
|
__caller_modules: ClassVar[dict[int, ModuleType]] = {}
|
|
295
276
|
|
|
@@ -298,9 +279,9 @@ class System:
|
|
|
298
279
|
pipes: Iterable[Iterable[type[Application]]],
|
|
299
280
|
):
|
|
300
281
|
# Remember the caller frame's module, so that we might identify a topic.
|
|
301
|
-
caller_frame = cast(FrameType,
|
|
302
|
-
module = cast(ModuleType, inspect.getmodule(caller_frame))
|
|
303
|
-
type(self).__caller_modules[id(self)] = module
|
|
282
|
+
caller_frame = cast("FrameType", inspect.currentframe()).f_back
|
|
283
|
+
module = cast("ModuleType", inspect.getmodule(caller_frame))
|
|
284
|
+
type(self).__caller_modules[id(self)] = module # noqa: SLF001
|
|
304
285
|
|
|
305
286
|
# Build nodes and edges.
|
|
306
287
|
self.edges: list[tuple[str, str]] = []
|
|
@@ -319,9 +300,8 @@ class System:
|
|
|
319
300
|
self.edges.append(edge)
|
|
320
301
|
|
|
321
302
|
self.nodes: dict[str, str] = {}
|
|
322
|
-
for name in classes:
|
|
323
|
-
|
|
324
|
-
self.nodes[name] = topic
|
|
303
|
+
for name, cls in classes.items():
|
|
304
|
+
self.nodes[name] = get_topic(cls)
|
|
325
305
|
|
|
326
306
|
# Identify leaders and followers.
|
|
327
307
|
self.follows: dict[str, list[str]] = defaultdict(list)
|
|
@@ -339,12 +319,14 @@ class System:
|
|
|
339
319
|
# Check followers are followers.
|
|
340
320
|
for name in self.follows:
|
|
341
321
|
if not issubclass(classes[name], Follower):
|
|
342
|
-
|
|
322
|
+
msg = f"Not a follower class: {classes[name]}"
|
|
323
|
+
raise TypeError(msg)
|
|
343
324
|
|
|
344
325
|
# Check each process is a process application class.
|
|
345
326
|
for name in self.processors:
|
|
346
327
|
if not issubclass(classes[name], ProcessApplication):
|
|
347
|
-
|
|
328
|
+
msg = f"Not a process application class: {classes[name]}"
|
|
329
|
+
raise TypeError(msg)
|
|
348
330
|
|
|
349
331
|
@property
|
|
350
332
|
def leaders(self) -> list[str]:
|
|
@@ -392,15 +374,13 @@ class System:
|
|
|
392
374
|
topic = module.__name__ + ":" + name
|
|
393
375
|
assert resolve_topic(topic) is self
|
|
394
376
|
if topic is None:
|
|
395
|
-
msg = "Unable to compute topic for system object:
|
|
377
|
+
msg = f"Unable to compute topic for system object: {self}"
|
|
396
378
|
raise ProgrammingError(msg)
|
|
397
379
|
return topic
|
|
398
380
|
|
|
399
381
|
|
|
400
382
|
class Runner(ABC):
|
|
401
|
-
"""
|
|
402
|
-
Abstract base class for system runners.
|
|
403
|
-
"""
|
|
383
|
+
"""Abstract base class for system runners."""
|
|
404
384
|
|
|
405
385
|
def __init__(self, system: System, env: EnvType | None = None):
|
|
406
386
|
self.system = system
|
|
@@ -409,24 +389,18 @@ class Runner(ABC):
|
|
|
409
389
|
|
|
410
390
|
@abstractmethod
|
|
411
391
|
def start(self) -> None:
|
|
412
|
-
"""
|
|
413
|
-
Starts the runner.
|
|
414
|
-
"""
|
|
392
|
+
"""Starts the runner."""
|
|
415
393
|
if self.is_started:
|
|
416
394
|
raise RunnerAlreadyStartedError
|
|
417
395
|
self.is_started = True
|
|
418
396
|
|
|
419
397
|
@abstractmethod
|
|
420
398
|
def stop(self) -> None:
|
|
421
|
-
"""
|
|
422
|
-
Stops the runner.
|
|
423
|
-
"""
|
|
399
|
+
"""Stops the runner."""
|
|
424
400
|
|
|
425
401
|
@abstractmethod
|
|
426
402
|
def get(self, cls: type[TApplication]) -> TApplication:
|
|
427
|
-
"""
|
|
428
|
-
Returns an application instance for given application class.
|
|
429
|
-
"""
|
|
403
|
+
"""Returns an application instance for given application class."""
|
|
430
404
|
|
|
431
405
|
def __enter__(self) -> Self:
|
|
432
406
|
self.start()
|
|
@@ -437,38 +411,26 @@ class Runner(ABC):
|
|
|
437
411
|
|
|
438
412
|
|
|
439
413
|
class RunnerAlreadyStartedError(Exception):
|
|
440
|
-
"""
|
|
441
|
-
Raised when runner is already started.
|
|
442
|
-
"""
|
|
414
|
+
"""Raised when runner is already started."""
|
|
443
415
|
|
|
444
416
|
|
|
445
417
|
class NotificationPullingError(Exception):
|
|
446
|
-
"""
|
|
447
|
-
Raised when pulling notifications fails.
|
|
448
|
-
"""
|
|
418
|
+
"""Raised when pulling notifications fails."""
|
|
449
419
|
|
|
450
420
|
|
|
451
421
|
class NotificationConvertingError(Exception):
|
|
452
|
-
"""
|
|
453
|
-
Raised when converting notifications fails.
|
|
454
|
-
"""
|
|
422
|
+
"""Raised when converting notifications fails."""
|
|
455
423
|
|
|
456
424
|
|
|
457
425
|
class EventProcessingError(Exception):
|
|
458
|
-
"""
|
|
459
|
-
Raised when event processing fails.
|
|
460
|
-
"""
|
|
426
|
+
"""Raised when event processing fails."""
|
|
461
427
|
|
|
462
428
|
|
|
463
429
|
class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
464
|
-
"""
|
|
465
|
-
Runs a :class:`System` in a single thread.
|
|
466
|
-
"""
|
|
430
|
+
"""Runs a :class:`System` in a single thread."""
|
|
467
431
|
|
|
468
432
|
def __init__(self, system: System, env: EnvType | None = None):
|
|
469
|
-
"""
|
|
470
|
-
Initialises runner with the given :class:`System`.
|
|
471
|
-
"""
|
|
433
|
+
"""Initialises runner with the given :class:`System`."""
|
|
472
434
|
super().__init__(system=system, env=env)
|
|
473
435
|
self.apps: dict[str, Application] = {}
|
|
474
436
|
self._recording_events_received: list[RecordingEvent] = []
|
|
@@ -491,34 +453,31 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
491
453
|
self.apps[name] = single
|
|
492
454
|
|
|
493
455
|
def start(self) -> None:
|
|
494
|
-
"""
|
|
495
|
-
Starts the runner. The applications mentioned in the system definition
|
|
456
|
+
"""Starts the runner. The applications mentioned in the system definition
|
|
496
457
|
are constructed. The followers are set up to follow the applications
|
|
497
458
|
they are defined as following in the system definition. And the leaders
|
|
498
459
|
are set up to lead the runner itself.
|
|
499
460
|
"""
|
|
500
|
-
|
|
501
461
|
super().start()
|
|
502
462
|
|
|
503
463
|
# Setup followers to follow leaders.
|
|
504
464
|
for edge in self.system.edges:
|
|
505
465
|
leader_name = edge[0]
|
|
506
466
|
follower_name = edge[1]
|
|
507
|
-
leader = cast(Leader, self.apps[leader_name])
|
|
508
|
-
follower = cast(Follower, self.apps[follower_name])
|
|
467
|
+
leader = cast("Leader", self.apps[leader_name])
|
|
468
|
+
follower = cast("Follower", self.apps[follower_name])
|
|
509
469
|
assert isinstance(leader, Leader)
|
|
510
470
|
assert isinstance(follower, Follower)
|
|
511
471
|
follower.follow(leader_name, leader.notification_log)
|
|
512
472
|
|
|
513
473
|
# Setup leaders to lead this runner.
|
|
514
474
|
for name in self.system.leaders:
|
|
515
|
-
leader = cast(Leader, self.apps[name])
|
|
475
|
+
leader = cast("Leader", self.apps[name])
|
|
516
476
|
assert isinstance(leader, Leader)
|
|
517
477
|
leader.lead(self)
|
|
518
478
|
|
|
519
|
-
def receive_recording_event(self,
|
|
520
|
-
"""
|
|
521
|
-
Receives recording event by appending the name of the leader
|
|
479
|
+
def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
|
|
480
|
+
"""Receives recording event by appending the name of the leader
|
|
522
481
|
to a list of prompted names.
|
|
523
482
|
|
|
524
483
|
Then, unless this method has previously been called and not yet returned,
|
|
@@ -528,7 +487,7 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
528
487
|
continues until there are no more prompted names. In this way, a system
|
|
529
488
|
of applications will process all events in a single thread.
|
|
530
489
|
"""
|
|
531
|
-
leader_name =
|
|
490
|
+
leader_name = new_recording_event.application_name
|
|
532
491
|
with self._prompted_names_lock:
|
|
533
492
|
self._prompted_names.add(leader_name)
|
|
534
493
|
|
|
@@ -544,7 +503,7 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
544
503
|
|
|
545
504
|
for leader_name in prompted_names:
|
|
546
505
|
for follower_name in self.system.leads[leader_name]:
|
|
547
|
-
follower = cast(Follower, self.apps[follower_name])
|
|
506
|
+
follower = cast("Follower", self.apps[follower_name])
|
|
548
507
|
follower.pull_and_process(leader_name)
|
|
549
508
|
|
|
550
509
|
finally:
|
|
@@ -562,14 +521,10 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
562
521
|
|
|
563
522
|
|
|
564
523
|
class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
565
|
-
"""
|
|
566
|
-
Runs a :class:`System` in a single thread.
|
|
567
|
-
"""
|
|
524
|
+
"""Runs a :class:`System` in a single thread."""
|
|
568
525
|
|
|
569
526
|
def __init__(self, system: System, env: EnvType | None = None):
|
|
570
|
-
"""
|
|
571
|
-
Initialises runner with the given :class:`System`.
|
|
572
|
-
"""
|
|
527
|
+
"""Initialises runner with the given :class:`System`."""
|
|
573
528
|
super().__init__(system=system, env=env)
|
|
574
529
|
self.apps: dict[str, Application] = {}
|
|
575
530
|
self._recording_events_received: list[RecordingEvent] = []
|
|
@@ -592,8 +547,7 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
592
547
|
self.apps[name] = single
|
|
593
548
|
|
|
594
549
|
def start(self) -> None:
|
|
595
|
-
"""
|
|
596
|
-
Starts the runner.
|
|
550
|
+
"""Starts the runner.
|
|
597
551
|
The applications are constructed, and setup to lead and follow
|
|
598
552
|
each other, according to the system definition.
|
|
599
553
|
The followers are setup to follow the applications they follow
|
|
@@ -601,28 +555,26 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
601
555
|
leader), and their leaders are setup to lead the runner itself
|
|
602
556
|
(send prompts).
|
|
603
557
|
"""
|
|
604
|
-
|
|
605
558
|
super().start()
|
|
606
559
|
|
|
607
560
|
# Setup followers to follow leaders.
|
|
608
561
|
for edge in self.system.edges:
|
|
609
562
|
leader_name = edge[0]
|
|
610
563
|
follower_name = edge[1]
|
|
611
|
-
leader = cast(Leader, self.apps[leader_name])
|
|
612
|
-
follower = cast(Follower, self.apps[follower_name])
|
|
564
|
+
leader = cast("Leader", self.apps[leader_name])
|
|
565
|
+
follower = cast("Follower", self.apps[follower_name])
|
|
613
566
|
assert isinstance(leader, Leader)
|
|
614
567
|
assert isinstance(follower, Follower)
|
|
615
568
|
follower.follow(leader_name, leader.notification_log)
|
|
616
569
|
|
|
617
570
|
# Setup leaders to notify followers.
|
|
618
571
|
for name in self.system.leaders:
|
|
619
|
-
leader = cast(Leader, self.apps[name])
|
|
572
|
+
leader = cast("Leader", self.apps[name])
|
|
620
573
|
assert isinstance(leader, Leader)
|
|
621
574
|
leader.lead(self)
|
|
622
575
|
|
|
623
|
-
def receive_recording_event(self,
|
|
624
|
-
"""
|
|
625
|
-
Receives recording event by appending it to list of received recording
|
|
576
|
+
def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
|
|
577
|
+
"""Receives recording event by appending it to list of received recording
|
|
626
578
|
events.
|
|
627
579
|
|
|
628
580
|
Unless this method has previously been called and not yet returned, it
|
|
@@ -630,19 +582,19 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
630
582
|
events, until there are none remaining.
|
|
631
583
|
"""
|
|
632
584
|
with self._recording_events_received_lock:
|
|
633
|
-
self._recording_events_received.append(
|
|
585
|
+
self._recording_events_received.append(new_recording_event)
|
|
634
586
|
|
|
635
587
|
if self._processing_lock.acquire(blocking=False):
|
|
636
588
|
try:
|
|
637
589
|
while True:
|
|
638
590
|
with self._recording_events_received_lock:
|
|
639
|
-
|
|
591
|
+
recording_events = self._recording_events_received
|
|
640
592
|
self._recording_events_received = []
|
|
641
593
|
|
|
642
|
-
if not
|
|
594
|
+
if not recording_events:
|
|
643
595
|
break
|
|
644
596
|
|
|
645
|
-
for recording_event in
|
|
597
|
+
for recording_event in recording_events:
|
|
646
598
|
leader_name = recording_event.application_name
|
|
647
599
|
previous_max_notification_id = (
|
|
648
600
|
self._previous_max_notification_ids.get(leader_name, 0)
|
|
@@ -709,15 +661,12 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
709
661
|
|
|
710
662
|
|
|
711
663
|
class MultiThreadedRunner(Runner):
|
|
712
|
-
"""
|
|
713
|
-
Runs a :class:`System` with one :class:`MultiThreadedRunnerThread`
|
|
664
|
+
"""Runs a :class:`System` with one :class:`MultiThreadedRunnerThread`
|
|
714
665
|
for each :class:`Follower` in the system definition.
|
|
715
666
|
"""
|
|
716
667
|
|
|
717
668
|
def __init__(self, system: System, env: EnvType | None = None):
|
|
718
|
-
"""
|
|
719
|
-
Initialises runner with the given :class:`System`.
|
|
720
|
-
"""
|
|
669
|
+
"""Initialises runner with the given :class:`System`."""
|
|
721
670
|
super().__init__(system=system, env=env)
|
|
722
671
|
self.apps: dict[str, Application] = {}
|
|
723
672
|
self.threads: dict[str, MultiThreadedRunnerThread] = {}
|
|
@@ -743,8 +692,7 @@ class MultiThreadedRunner(Runner):
|
|
|
743
692
|
self.apps[name] = single
|
|
744
693
|
|
|
745
694
|
def start(self) -> None:
|
|
746
|
-
"""
|
|
747
|
-
Starts the runner.
|
|
695
|
+
"""Starts the runner.
|
|
748
696
|
A multi-threaded runner thread is started for each
|
|
749
697
|
'follower' application in the system, and constructs
|
|
750
698
|
an instance of each non-follower leader application in
|
|
@@ -757,7 +705,7 @@ class MultiThreadedRunner(Runner):
|
|
|
757
705
|
|
|
758
706
|
# Construct followers.
|
|
759
707
|
for follower_name in self.system.followers:
|
|
760
|
-
follower = cast(Follower, self.apps[follower_name])
|
|
708
|
+
follower = cast("Follower", self.apps[follower_name])
|
|
761
709
|
|
|
762
710
|
thread = MultiThreadedRunnerThread(
|
|
763
711
|
follower=follower,
|
|
@@ -772,8 +720,8 @@ class MultiThreadedRunner(Runner):
|
|
|
772
720
|
|
|
773
721
|
# Lead and follow.
|
|
774
722
|
for edge in self.system.edges:
|
|
775
|
-
leader = cast(Leader, self.apps[edge[0]])
|
|
776
|
-
follower = cast(Follower, self.apps[edge[1]])
|
|
723
|
+
leader = cast("Leader", self.apps[edge[0]])
|
|
724
|
+
follower = cast("Follower", self.apps[edge[1]])
|
|
777
725
|
follower.follow(leader.name, leader.notification_log)
|
|
778
726
|
thread = self.threads[follower.name]
|
|
779
727
|
leader.lead(thread)
|
|
@@ -806,8 +754,7 @@ class MultiThreadedRunner(Runner):
|
|
|
806
754
|
|
|
807
755
|
|
|
808
756
|
class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
|
|
809
|
-
"""
|
|
810
|
-
Runs one :class:`~eventsourcing.system.Follower` application in
|
|
757
|
+
"""Runs one :class:`~eventsourcing.system.Follower` application in
|
|
811
758
|
a :class:`~eventsourcing.system.MultiThreadedRunner`.
|
|
812
759
|
"""
|
|
813
760
|
|
|
@@ -828,8 +775,7 @@ class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
|
|
|
828
775
|
self.is_running = threading.Event()
|
|
829
776
|
|
|
830
777
|
def run(self) -> None:
|
|
831
|
-
"""
|
|
832
|
-
Loops forever until stopped. The loop blocks on waiting
|
|
778
|
+
"""Loops forever until stopped. The loop blocks on waiting
|
|
833
779
|
for the 'is_prompted' event to be set, then calls
|
|
834
780
|
:func:`~Follower.pull_and_process` method for each
|
|
835
781
|
prompted name.
|
|
@@ -851,12 +797,11 @@ class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
|
|
|
851
797
|
self.error.__cause__ = e
|
|
852
798
|
self.has_errored.set()
|
|
853
799
|
|
|
854
|
-
def receive_recording_event(self,
|
|
855
|
-
"""
|
|
856
|
-
Receives prompt by appending name of
|
|
800
|
+
def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
|
|
801
|
+
"""Receives prompt by appending name of
|
|
857
802
|
leader to list of prompted names.
|
|
858
803
|
"""
|
|
859
|
-
leader_name =
|
|
804
|
+
leader_name = new_recording_event.application_name
|
|
860
805
|
with self.prompted_names_lock:
|
|
861
806
|
if leader_name not in self.prompted_names:
|
|
862
807
|
self.prompted_names.append(leader_name)
|
|
@@ -868,9 +813,7 @@ class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
|
|
|
868
813
|
|
|
869
814
|
|
|
870
815
|
class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
871
|
-
"""
|
|
872
|
-
Runs a :class:`System` with multiple threads in a new way.
|
|
873
|
-
"""
|
|
816
|
+
"""Runs a :class:`System` with multiple threads in a new way."""
|
|
874
817
|
|
|
875
818
|
QUEUE_MAX_SIZE: int = 0
|
|
876
819
|
|
|
@@ -879,9 +822,7 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
879
822
|
system: System,
|
|
880
823
|
env: EnvType | None = None,
|
|
881
824
|
):
|
|
882
|
-
"""
|
|
883
|
-
Initialises runner with the given :class:`System`.
|
|
884
|
-
"""
|
|
825
|
+
"""Initialises runner with the given :class:`System`."""
|
|
885
826
|
super().__init__(system=system, env=env)
|
|
886
827
|
self.apps: dict[str, Application] = {}
|
|
887
828
|
self.pulling_threads: dict[str, list[PullingThread]] = {}
|
|
@@ -909,8 +850,7 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
909
850
|
self.apps[name] = single
|
|
910
851
|
|
|
911
852
|
def start(self) -> None:
|
|
912
|
-
"""
|
|
913
|
-
Starts the runner.
|
|
853
|
+
"""Starts the runner.
|
|
914
854
|
|
|
915
855
|
A multi-threaded runner thread is started for each
|
|
916
856
|
'follower' application in the system, and constructs
|
|
@@ -924,7 +864,7 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
924
864
|
|
|
925
865
|
# Start the processing threads.
|
|
926
866
|
for follower_name in self.system.followers:
|
|
927
|
-
follower = cast(Follower, self.apps[follower_name])
|
|
867
|
+
follower = cast("Follower", self.apps[follower_name])
|
|
928
868
|
processing_queue: Queue[list[ProcessingJob] | None] = Queue(
|
|
929
869
|
maxsize=self.QUEUE_MAX_SIZE
|
|
930
870
|
)
|
|
@@ -940,9 +880,9 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
940
880
|
for edge in self.system.edges:
|
|
941
881
|
# Set up follower to pull notifications from leader.
|
|
942
882
|
leader_name = edge[0]
|
|
943
|
-
leader = cast(Leader, self.apps[leader_name])
|
|
883
|
+
leader = cast("Leader", self.apps[leader_name])
|
|
944
884
|
follower_name = edge[1]
|
|
945
|
-
follower = cast(Follower, self.apps[follower_name])
|
|
885
|
+
follower = cast("Follower", self.apps[follower_name])
|
|
946
886
|
follower.follow(leader.name, leader.notification_log)
|
|
947
887
|
|
|
948
888
|
# Create converting queue.
|
|
@@ -978,7 +918,7 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
978
918
|
|
|
979
919
|
# Subscribe for notifications from leaders.
|
|
980
920
|
for leader_name in self.system.leaders:
|
|
981
|
-
leader = cast(Leader, self.apps[leader_name])
|
|
921
|
+
leader = cast("Leader", self.apps[leader_name])
|
|
982
922
|
assert isinstance(leader, Leader)
|
|
983
923
|
leader.lead(self)
|
|
984
924
|
|
|
@@ -1007,14 +947,15 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
1007
947
|
assert isinstance(app, cls)
|
|
1008
948
|
return app
|
|
1009
949
|
|
|
1010
|
-
def receive_recording_event(self,
|
|
1011
|
-
for pulling_thread in self.pulling_threads[
|
|
1012
|
-
|
|
950
|
+
def receive_recording_event(self, new_recording_event: RecordingEvent) -> None:
|
|
951
|
+
for pulling_thread in self.pulling_threads[
|
|
952
|
+
new_recording_event.application_name
|
|
953
|
+
]:
|
|
954
|
+
pulling_thread.receive_recording_event(new_recording_event)
|
|
1013
955
|
|
|
1014
956
|
|
|
1015
957
|
class PullingThread(threading.Thread):
|
|
1016
|
-
"""
|
|
1017
|
-
Receives or pulls notifications from the given leader, and
|
|
958
|
+
"""Receives or pulls notifications from the given leader, and
|
|
1018
959
|
puts them on a queue for conversion into processing jobs.
|
|
1019
960
|
"""
|
|
1020
961
|
|
|
@@ -1096,9 +1037,7 @@ class PullingThread(threading.Thread):
|
|
|
1096
1037
|
|
|
1097
1038
|
|
|
1098
1039
|
class ConvertingThread(threading.Thread):
|
|
1099
|
-
"""
|
|
1100
|
-
Converts notifications into processing jobs.
|
|
1101
|
-
"""
|
|
1040
|
+
"""Converts notifications into processing jobs."""
|
|
1102
1041
|
|
|
1103
1042
|
def __init__(
|
|
1104
1043
|
self,
|
|
@@ -1155,7 +1094,7 @@ class ConvertingThread(threading.Thread):
|
|
|
1155
1094
|
if processing_jobs:
|
|
1156
1095
|
self.processing_queue.put(processing_jobs)
|
|
1157
1096
|
except Exception as e:
|
|
1158
|
-
print(traceback.format_exc())
|
|
1097
|
+
print(traceback.format_exc()) # noqa: T201
|
|
1159
1098
|
self.error = NotificationConvertingError(str(e))
|
|
1160
1099
|
self.error.__cause__ = e
|
|
1161
1100
|
self.has_errored.set()
|
|
@@ -1166,8 +1105,7 @@ class ConvertingThread(threading.Thread):
|
|
|
1166
1105
|
|
|
1167
1106
|
|
|
1168
1107
|
class ProcessingThread(threading.Thread):
|
|
1169
|
-
"""
|
|
1170
|
-
A processing thread gets events from a processing queue, and
|
|
1108
|
+
"""A processing thread gets events from a processing queue, and
|
|
1171
1109
|
calls the application's process_event() method.
|
|
1172
1110
|
"""
|
|
1173
1111
|
|
|
@@ -1206,9 +1144,7 @@ class ProcessingThread(threading.Thread):
|
|
|
1206
1144
|
|
|
1207
1145
|
|
|
1208
1146
|
class NotificationLogReader:
|
|
1209
|
-
"""
|
|
1210
|
-
Reads domain event notifications from a notification log.
|
|
1211
|
-
"""
|
|
1147
|
+
"""Reads domain event notifications from a notification log."""
|
|
1212
1148
|
|
|
1213
1149
|
DEFAULT_SECTION_SIZE = 10
|
|
1214
1150
|
|
|
@@ -1217,8 +1153,7 @@ class NotificationLogReader:
|
|
|
1217
1153
|
notification_log: NotificationLog,
|
|
1218
1154
|
section_size: int = DEFAULT_SECTION_SIZE,
|
|
1219
1155
|
):
|
|
1220
|
-
"""
|
|
1221
|
-
Initialises a reader with the given notification log,
|
|
1156
|
+
"""Initialises a reader with the given notification log,
|
|
1222
1157
|
and optionally a section size integer which determines
|
|
1223
1158
|
the requested number of domain event notifications in
|
|
1224
1159
|
each section retrieved from the notification log.
|
|
@@ -1227,8 +1162,7 @@ class NotificationLogReader:
|
|
|
1227
1162
|
self.section_size = section_size
|
|
1228
1163
|
|
|
1229
1164
|
def read(self, *, start: int) -> Iterator[Notification]:
|
|
1230
|
-
"""
|
|
1231
|
-
Returns a generator that yields event notifications
|
|
1165
|
+
"""Returns a generator that yields event notifications
|
|
1232
1166
|
from the reader's notification log, starting from
|
|
1233
1167
|
given start position (a notification ID).
|
|
1234
1168
|
|
|
@@ -1257,8 +1191,7 @@ class NotificationLogReader:
|
|
|
1257
1191
|
topics: Sequence[str] = (),
|
|
1258
1192
|
inclusive_of_start: bool = True,
|
|
1259
1193
|
) -> Iterator[list[Notification]]:
|
|
1260
|
-
"""
|
|
1261
|
-
Returns a generator that yields lists of event notifications
|
|
1194
|
+
"""Returns a generator that yields lists of event notifications
|
|
1262
1195
|
from the reader's notification log, starting from given start
|
|
1263
1196
|
position (a notification ID).
|
|
1264
1197
|
|