eventsourcing 9.4.0a4__py3-none-any.whl → 9.4.0a6__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 +21 -27
- eventsourcing/domain.py +49 -51
- eventsourcing/interface.py +5 -2
- eventsourcing/persistence.py +29 -36
- eventsourcing/popo.py +17 -16
- eventsourcing/postgres.py +18 -17
- eventsourcing/projection.py +17 -26
- eventsourcing/sqlite.py +18 -17
- eventsourcing/system.py +49 -62
- eventsourcing/tests/application.py +3 -3
- eventsourcing/tests/persistence.py +12 -12
- eventsourcing/utils.py +8 -20
- {eventsourcing-9.4.0a4.dist-info → eventsourcing-9.4.0a6.dist-info}/METADATA +4 -6
- eventsourcing-9.4.0a6.dist-info/RECORD +26 -0
- {eventsourcing-9.4.0a4.dist-info → eventsourcing-9.4.0a6.dist-info}/WHEEL +1 -1
- eventsourcing-9.4.0a4.dist-info/RECORD +0 -26
- {eventsourcing-9.4.0a4.dist-info → eventsourcing-9.4.0a6.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.4.0a4.dist-info → eventsourcing-9.4.0a6.dist-info}/LICENSE +0 -0
eventsourcing/sqlite.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sqlite3
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
from eventsourcing.persistence import (
|
|
@@ -32,6 +32,7 @@ from eventsourcing.persistence import (
|
|
|
32
32
|
from eventsourcing.utils import Environment, resolve_topic, strtobool
|
|
33
33
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
35
|
+
from collections.abc import Iterator, Sequence
|
|
35
36
|
from types import TracebackType
|
|
36
37
|
|
|
37
38
|
SQLITE3_DEFAULT_LOCK_TIMEOUT = 5
|
|
@@ -103,7 +104,7 @@ class SQLiteTransaction:
|
|
|
103
104
|
|
|
104
105
|
def __exit__(
|
|
105
106
|
self,
|
|
106
|
-
exc_type:
|
|
107
|
+
exc_type: type[BaseException] | None,
|
|
107
108
|
exc_val: BaseException | None,
|
|
108
109
|
exc_tb: TracebackType | None,
|
|
109
110
|
) -> None:
|
|
@@ -254,7 +255,7 @@ class SQLiteRecorder(Recorder):
|
|
|
254
255
|
self.datastore = datastore
|
|
255
256
|
self.create_table_statements = self.construct_create_table_statements()
|
|
256
257
|
|
|
257
|
-
def construct_create_table_statements(self) ->
|
|
258
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
258
259
|
return []
|
|
259
260
|
|
|
260
261
|
def create_table(self) -> None:
|
|
@@ -278,7 +279,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
278
279
|
f"SELECT * FROM {self.events_table_name} WHERE originator_id=? "
|
|
279
280
|
)
|
|
280
281
|
|
|
281
|
-
def construct_create_table_statements(self) ->
|
|
282
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
282
283
|
statements = super().construct_create_table_statements()
|
|
283
284
|
statements.append(
|
|
284
285
|
"CREATE TABLE IF NOT EXISTS "
|
|
@@ -294,7 +295,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
294
295
|
return statements
|
|
295
296
|
|
|
296
297
|
def insert_events(
|
|
297
|
-
self, stored_events:
|
|
298
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
298
299
|
) -> Sequence[int] | None:
|
|
299
300
|
with self.datastore.transaction(commit=True) as c:
|
|
300
301
|
return self._insert_events(c, stored_events, **kwargs)
|
|
@@ -302,7 +303,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
302
303
|
def _insert_events(
|
|
303
304
|
self,
|
|
304
305
|
c: SQLiteCursor,
|
|
305
|
-
stored_events:
|
|
306
|
+
stored_events: list[StoredEvent],
|
|
306
307
|
**_: Any,
|
|
307
308
|
) -> Sequence[int] | None:
|
|
308
309
|
params = [
|
|
@@ -325,9 +326,9 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
325
326
|
lte: int | None = None,
|
|
326
327
|
desc: bool = False,
|
|
327
328
|
limit: int | None = None,
|
|
328
|
-
) ->
|
|
329
|
+
) -> list[StoredEvent]:
|
|
329
330
|
statement = self.select_events_statement
|
|
330
|
-
params:
|
|
331
|
+
params: list[Any] = [originator_id.hex]
|
|
331
332
|
if gt is not None:
|
|
332
333
|
statement += "AND originator_version>? "
|
|
333
334
|
params.append(gt)
|
|
@@ -369,7 +370,7 @@ class SQLiteApplicationRecorder(
|
|
|
369
370
|
f"SELECT MAX(rowid) FROM {self.events_table_name}"
|
|
370
371
|
)
|
|
371
372
|
|
|
372
|
-
def construct_create_table_statements(self) ->
|
|
373
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
373
374
|
statement = (
|
|
374
375
|
"CREATE TABLE IF NOT EXISTS "
|
|
375
376
|
f"{self.events_table_name} ("
|
|
@@ -385,7 +386,7 @@ class SQLiteApplicationRecorder(
|
|
|
385
386
|
def _insert_events(
|
|
386
387
|
self,
|
|
387
388
|
c: SQLiteCursor,
|
|
388
|
-
stored_events:
|
|
389
|
+
stored_events: list[StoredEvent],
|
|
389
390
|
**_: Any,
|
|
390
391
|
) -> Sequence[int] | None:
|
|
391
392
|
returning = []
|
|
@@ -410,12 +411,12 @@ class SQLiteApplicationRecorder(
|
|
|
410
411
|
topics: Sequence[str] = (),
|
|
411
412
|
*,
|
|
412
413
|
inclusive_of_start: bool = True,
|
|
413
|
-
) ->
|
|
414
|
+
) -> list[Notification]:
|
|
414
415
|
"""
|
|
415
416
|
Returns a list of event notifications
|
|
416
417
|
from 'start', limited by 'limit'.
|
|
417
418
|
"""
|
|
418
|
-
params:
|
|
419
|
+
params: list[int | str] = []
|
|
419
420
|
statement = f"SELECT rowid, * FROM {self.events_table_name} "
|
|
420
421
|
has_where = False
|
|
421
422
|
if start is not None:
|
|
@@ -494,7 +495,7 @@ class SQLiteTrackingRecorder(SQLiteRecorder, TrackingRecorder):
|
|
|
494
495
|
"application_name=? AND notification_id=?"
|
|
495
496
|
)
|
|
496
497
|
|
|
497
|
-
def construct_create_table_statements(self) ->
|
|
498
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
498
499
|
statements = super().construct_create_table_statements()
|
|
499
500
|
statements.append(
|
|
500
501
|
"CREATE TABLE IF NOT EXISTS tracking ("
|
|
@@ -552,7 +553,7 @@ class SQLiteProcessRecorder(
|
|
|
552
553
|
def _insert_events(
|
|
553
554
|
self,
|
|
554
555
|
c: SQLiteCursor,
|
|
555
|
-
stored_events:
|
|
556
|
+
stored_events: list[StoredEvent],
|
|
556
557
|
**kwargs: Any,
|
|
557
558
|
) -> Sequence[int] | None:
|
|
558
559
|
returning = super()._insert_events(c, stored_events, **kwargs)
|
|
@@ -616,7 +617,7 @@ class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
|
616
617
|
application_recorder_topic = self.env.get(self.APPLICATION_RECORDER_TOPIC)
|
|
617
618
|
|
|
618
619
|
if application_recorder_topic:
|
|
619
|
-
application_recorder_class:
|
|
620
|
+
application_recorder_class: type[SQLiteApplicationRecorder] = resolve_topic(
|
|
620
621
|
application_recorder_topic
|
|
621
622
|
)
|
|
622
623
|
assert issubclass(application_recorder_class, SQLiteApplicationRecorder)
|
|
@@ -630,7 +631,7 @@ class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
|
630
631
|
return recorder
|
|
631
632
|
|
|
632
633
|
def tracking_recorder(
|
|
633
|
-
self, tracking_recorder_class:
|
|
634
|
+
self, tracking_recorder_class: type[SQLiteTrackingRecorder] | None = None
|
|
634
635
|
) -> SQLiteTrackingRecorder:
|
|
635
636
|
if tracking_recorder_class is None:
|
|
636
637
|
tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
|
|
@@ -653,7 +654,7 @@ class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
|
653
654
|
process_recorder_topic = self.env.get(self.PROCESS_RECORDER_TOPIC)
|
|
654
655
|
|
|
655
656
|
if process_recorder_topic:
|
|
656
|
-
process_recorder_class:
|
|
657
|
+
process_recorder_class: type[SQLiteProcessRecorder] = resolve_topic(
|
|
657
658
|
process_recorder_topic
|
|
658
659
|
)
|
|
659
660
|
assert issubclass(process_recorder_class, SQLiteProcessRecorder)
|
eventsourcing/system.py
CHANGED
|
@@ -7,23 +7,10 @@ from abc import ABC, abstractmethod
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from queue import Full, Queue
|
|
9
9
|
from types import FrameType, ModuleType
|
|
10
|
-
from typing import
|
|
11
|
-
TYPE_CHECKING,
|
|
12
|
-
Any,
|
|
13
|
-
ClassVar,
|
|
14
|
-
Dict,
|
|
15
|
-
Iterable,
|
|
16
|
-
Iterator,
|
|
17
|
-
List,
|
|
18
|
-
Optional,
|
|
19
|
-
Sequence,
|
|
20
|
-
Tuple,
|
|
21
|
-
Type,
|
|
22
|
-
Union,
|
|
23
|
-
cast,
|
|
24
|
-
)
|
|
10
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast
|
|
25
11
|
|
|
26
12
|
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
27
14
|
from typing_extensions import Self
|
|
28
15
|
|
|
29
16
|
from eventsourcing.application import (
|
|
@@ -44,14 +31,14 @@ from eventsourcing.persistence import (
|
|
|
44
31
|
)
|
|
45
32
|
from eventsourcing.utils import EnvType, get_topic, resolve_topic
|
|
46
33
|
|
|
47
|
-
ProcessingJob =
|
|
34
|
+
ProcessingJob = tuple[DomainEventProtocol, Tracking]
|
|
48
35
|
|
|
49
36
|
|
|
50
37
|
class RecordingEvent:
|
|
51
38
|
def __init__(
|
|
52
39
|
self,
|
|
53
40
|
application_name: str,
|
|
54
|
-
recordings:
|
|
41
|
+
recordings: list[Recording],
|
|
55
42
|
previous_max_notification_id: int | None,
|
|
56
43
|
):
|
|
57
44
|
self.application_name = application_name
|
|
@@ -59,7 +46,7 @@ class RecordingEvent:
|
|
|
59
46
|
self.previous_max_notification_id = previous_max_notification_id
|
|
60
47
|
|
|
61
48
|
|
|
62
|
-
ConvertingJob = Optional[Union[RecordingEvent,
|
|
49
|
+
ConvertingJob = Optional[Union[RecordingEvent, list[Notification]]]
|
|
63
50
|
|
|
64
51
|
|
|
65
52
|
class Follower(Application):
|
|
@@ -75,8 +62,8 @@ class Follower(Application):
|
|
|
75
62
|
|
|
76
63
|
def __init__(self, env: EnvType | None = None) -> None:
|
|
77
64
|
super().__init__(env)
|
|
78
|
-
self.readers:
|
|
79
|
-
self.mappers:
|
|
65
|
+
self.readers: dict[str, NotificationLogReader] = {}
|
|
66
|
+
self.mappers: dict[str, Mapper] = {}
|
|
80
67
|
self.recorder: ProcessRecorder
|
|
81
68
|
self.is_threading_enabled = False
|
|
82
69
|
self.processing_lock = threading.Lock()
|
|
@@ -129,7 +116,7 @@ class Follower(Application):
|
|
|
129
116
|
stop: int | None = None,
|
|
130
117
|
*,
|
|
131
118
|
inclusive_of_start: bool = True,
|
|
132
|
-
) -> Iterator[
|
|
119
|
+
) -> Iterator[list[Notification]]:
|
|
133
120
|
"""
|
|
134
121
|
Pulls batches of unseen :class:`~eventsourcing.persistence.Notification`
|
|
135
122
|
objects from the notification log reader of the named application.
|
|
@@ -142,15 +129,15 @@ class Follower(Application):
|
|
|
142
129
|
)
|
|
143
130
|
|
|
144
131
|
def filter_received_notifications(
|
|
145
|
-
self, notifications:
|
|
146
|
-
) ->
|
|
132
|
+
self, notifications: list[Notification]
|
|
133
|
+
) -> list[Notification]:
|
|
147
134
|
if self.follow_topics:
|
|
148
135
|
return [n for n in notifications if n.topic in self.follow_topics]
|
|
149
136
|
return notifications
|
|
150
137
|
|
|
151
138
|
def convert_notifications(
|
|
152
139
|
self, leader_name: str, notifications: Iterable[Notification]
|
|
153
|
-
) ->
|
|
140
|
+
) -> list[ProcessingJob]:
|
|
154
141
|
"""
|
|
155
142
|
Uses the given :class:`~eventsourcing.persistence.Mapper` to convert
|
|
156
143
|
each received :class:`~eventsourcing.persistence.Notification`
|
|
@@ -252,7 +239,7 @@ class Leader(Application):
|
|
|
252
239
|
def __init__(self, env: EnvType | None = None) -> None:
|
|
253
240
|
super().__init__(env)
|
|
254
241
|
self.previous_max_notification_id: int | None = None
|
|
255
|
-
self.followers:
|
|
242
|
+
self.followers: list[RecordingEventReceiver] = []
|
|
256
243
|
|
|
257
244
|
def lead(self, follower: RecordingEventReceiver) -> None:
|
|
258
245
|
"""
|
|
@@ -264,12 +251,12 @@ class Leader(Application):
|
|
|
264
251
|
self,
|
|
265
252
|
*objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
|
|
266
253
|
**kwargs: Any,
|
|
267
|
-
) ->
|
|
254
|
+
) -> list[Recording]:
|
|
268
255
|
if self.previous_max_notification_id is None:
|
|
269
256
|
self.previous_max_notification_id = self.recorder.max_notification_id()
|
|
270
257
|
return super().save(*objs, **kwargs)
|
|
271
258
|
|
|
272
|
-
def _notify(self, recordings:
|
|
259
|
+
def _notify(self, recordings: list[Recording]) -> None:
|
|
273
260
|
"""
|
|
274
261
|
Calls :func:`receive_recording_event` on each follower
|
|
275
262
|
whenever new events have just been saved.
|
|
@@ -302,11 +289,11 @@ class System:
|
|
|
302
289
|
Defines a system of applications.
|
|
303
290
|
"""
|
|
304
291
|
|
|
305
|
-
__caller_modules: ClassVar[
|
|
292
|
+
__caller_modules: ClassVar[dict[int, ModuleType]] = {}
|
|
306
293
|
|
|
307
294
|
def __init__(
|
|
308
295
|
self,
|
|
309
|
-
pipes: Iterable[Iterable[
|
|
296
|
+
pipes: Iterable[Iterable[type[Application]]],
|
|
310
297
|
):
|
|
311
298
|
# Remember the caller frame's module, so that we might identify a topic.
|
|
312
299
|
caller_frame = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back)
|
|
@@ -314,8 +301,8 @@ class System:
|
|
|
314
301
|
type(self).__caller_modules[id(self)] = module
|
|
315
302
|
|
|
316
303
|
# Build nodes and edges.
|
|
317
|
-
self.edges:
|
|
318
|
-
classes:
|
|
304
|
+
self.edges: list[tuple[str, str]] = []
|
|
305
|
+
classes: dict[str, type[Application]] = {}
|
|
319
306
|
for pipe in pipes:
|
|
320
307
|
follower_cls = None
|
|
321
308
|
for cls in pipe:
|
|
@@ -329,14 +316,14 @@ class System:
|
|
|
329
316
|
if edge not in self.edges:
|
|
330
317
|
self.edges.append(edge)
|
|
331
318
|
|
|
332
|
-
self.nodes:
|
|
319
|
+
self.nodes: dict[str, str] = {}
|
|
333
320
|
for name in classes:
|
|
334
321
|
topic = get_topic(classes[name])
|
|
335
322
|
self.nodes[name] = topic
|
|
336
323
|
|
|
337
324
|
# Identify leaders and followers.
|
|
338
|
-
self.follows:
|
|
339
|
-
self.leads:
|
|
325
|
+
self.follows: dict[str, list[str]] = defaultdict(list)
|
|
326
|
+
self.leads: dict[str, list[str]] = defaultdict(list)
|
|
340
327
|
for edge in self.edges:
|
|
341
328
|
self.leads[edge[0]].append(edge[1])
|
|
342
329
|
self.follows[edge[1]].append(edge[0])
|
|
@@ -358,27 +345,27 @@ class System:
|
|
|
358
345
|
raise TypeError("Not a process application class: %s" % classes[name])
|
|
359
346
|
|
|
360
347
|
@property
|
|
361
|
-
def leaders(self) ->
|
|
348
|
+
def leaders(self) -> list[str]:
|
|
362
349
|
return list(self.leads.keys())
|
|
363
350
|
|
|
364
351
|
@property
|
|
365
|
-
def leaders_only(self) ->
|
|
352
|
+
def leaders_only(self) -> list[str]:
|
|
366
353
|
return [name for name in self.leads if name not in self.follows]
|
|
367
354
|
|
|
368
355
|
@property
|
|
369
|
-
def followers(self) ->
|
|
356
|
+
def followers(self) -> list[str]:
|
|
370
357
|
return list(self.follows.keys())
|
|
371
358
|
|
|
372
359
|
@property
|
|
373
|
-
def processors(self) ->
|
|
360
|
+
def processors(self) -> list[str]:
|
|
374
361
|
return [name for name in self.leads if name in self.follows]
|
|
375
362
|
|
|
376
|
-
def get_app_cls(self, name: str) ->
|
|
363
|
+
def get_app_cls(self, name: str) -> type[Application]:
|
|
377
364
|
cls = resolve_topic(self.nodes[name])
|
|
378
365
|
assert issubclass(cls, Application)
|
|
379
366
|
return cls
|
|
380
367
|
|
|
381
|
-
def leader_cls(self, name: str) ->
|
|
368
|
+
def leader_cls(self, name: str) -> type[Leader]:
|
|
382
369
|
cls = self.get_app_cls(name)
|
|
383
370
|
if issubclass(cls, Leader):
|
|
384
371
|
return cls
|
|
@@ -386,7 +373,7 @@ class System:
|
|
|
386
373
|
assert issubclass(cls, Leader)
|
|
387
374
|
return cls
|
|
388
375
|
|
|
389
|
-
def follower_cls(self, name: str) ->
|
|
376
|
+
def follower_cls(self, name: str) -> type[Follower]:
|
|
390
377
|
cls = self.get_app_cls(name)
|
|
391
378
|
assert issubclass(cls, Follower)
|
|
392
379
|
return cls
|
|
@@ -431,7 +418,7 @@ class Runner(ABC):
|
|
|
431
418
|
"""
|
|
432
419
|
|
|
433
420
|
@abstractmethod
|
|
434
|
-
def get(self, cls:
|
|
421
|
+
def get(self, cls: type[TApplication]) -> TApplication:
|
|
435
422
|
"""
|
|
436
423
|
Returns an application instance for given application class.
|
|
437
424
|
"""
|
|
@@ -471,8 +458,8 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
471
458
|
Initialises runner with the given :class:`System`.
|
|
472
459
|
"""
|
|
473
460
|
super().__init__(system=system, env=env)
|
|
474
|
-
self.apps:
|
|
475
|
-
self._recording_events_received:
|
|
461
|
+
self.apps: dict[str, Application] = {}
|
|
462
|
+
self._recording_events_received: list[RecordingEvent] = []
|
|
476
463
|
self._prompted_names_lock = threading.Lock()
|
|
477
464
|
self._prompted_names: set[str] = set()
|
|
478
465
|
self._processing_lock = threading.Lock()
|
|
@@ -556,7 +543,7 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
556
543
|
app.close()
|
|
557
544
|
self.apps.clear()
|
|
558
545
|
|
|
559
|
-
def get(self, cls:
|
|
546
|
+
def get(self, cls: type[TApplication]) -> TApplication:
|
|
560
547
|
app = self.apps[cls.name]
|
|
561
548
|
assert isinstance(app, cls)
|
|
562
549
|
return app
|
|
@@ -579,11 +566,11 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
579
566
|
Initialises runner with the given :class:`System`.
|
|
580
567
|
"""
|
|
581
568
|
super().__init__(system=system, env=env)
|
|
582
|
-
self.apps:
|
|
583
|
-
self._recording_events_received:
|
|
569
|
+
self.apps: dict[str, Application] = {}
|
|
570
|
+
self._recording_events_received: list[RecordingEvent] = []
|
|
584
571
|
self._recording_events_received_lock = threading.Lock()
|
|
585
572
|
self._processing_lock = threading.Lock()
|
|
586
|
-
self._previous_max_notification_ids:
|
|
573
|
+
self._previous_max_notification_ids: dict[str, int] = {}
|
|
587
574
|
|
|
588
575
|
# Construct followers.
|
|
589
576
|
for name in self.system.followers:
|
|
@@ -710,7 +697,7 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
710
697
|
app.close()
|
|
711
698
|
self.apps.clear()
|
|
712
699
|
|
|
713
|
-
def get(self, cls:
|
|
700
|
+
def get(self, cls: type[TApplication]) -> TApplication:
|
|
714
701
|
app = self.apps[cls.name]
|
|
715
702
|
assert isinstance(app, cls)
|
|
716
703
|
return app
|
|
@@ -727,8 +714,8 @@ class MultiThreadedRunner(Runner):
|
|
|
727
714
|
Initialises runner with the given :class:`System`.
|
|
728
715
|
"""
|
|
729
716
|
super().__init__(system=system, env=env)
|
|
730
|
-
self.apps:
|
|
731
|
-
self.threads:
|
|
717
|
+
self.apps: dict[str, Application] = {}
|
|
718
|
+
self.threads: dict[str, MultiThreadedRunnerThread] = {}
|
|
732
719
|
self.has_errored = threading.Event()
|
|
733
720
|
|
|
734
721
|
# Construct followers.
|
|
@@ -807,7 +794,7 @@ class MultiThreadedRunner(Runner):
|
|
|
807
794
|
if thread.error:
|
|
808
795
|
raise thread.error
|
|
809
796
|
|
|
810
|
-
def get(self, cls:
|
|
797
|
+
def get(self, cls: type[TApplication]) -> TApplication:
|
|
811
798
|
app = self.apps[cls.name]
|
|
812
799
|
assert isinstance(app, cls)
|
|
813
800
|
return app
|
|
@@ -831,7 +818,7 @@ class MultiThreadedRunnerThread(RecordingEventReceiver, threading.Thread):
|
|
|
831
818
|
self.is_stopping = threading.Event()
|
|
832
819
|
self.has_started = threading.Event()
|
|
833
820
|
self.is_prompted = threading.Event()
|
|
834
|
-
self.prompted_names:
|
|
821
|
+
self.prompted_names: list[str] = []
|
|
835
822
|
self.prompted_names_lock = threading.Lock()
|
|
836
823
|
self.is_running = threading.Event()
|
|
837
824
|
|
|
@@ -891,10 +878,10 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
891
878
|
Initialises runner with the given :class:`System`.
|
|
892
879
|
"""
|
|
893
880
|
super().__init__(system=system, env=env)
|
|
894
|
-
self.apps:
|
|
895
|
-
self.pulling_threads:
|
|
896
|
-
self.processing_queues:
|
|
897
|
-
self.all_threads:
|
|
881
|
+
self.apps: dict[str, Application] = {}
|
|
882
|
+
self.pulling_threads: dict[str, list[PullingThread]] = {}
|
|
883
|
+
self.processing_queues: dict[str, Queue[list[ProcessingJob] | None]] = {}
|
|
884
|
+
self.all_threads: list[PullingThread | ConvertingThread | ProcessingThread] = []
|
|
898
885
|
self.has_errored = threading.Event()
|
|
899
886
|
|
|
900
887
|
# Construct followers.
|
|
@@ -933,7 +920,7 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
933
920
|
# Start the processing threads.
|
|
934
921
|
for follower_name in self.system.followers:
|
|
935
922
|
follower = cast(Follower, self.apps[follower_name])
|
|
936
|
-
processing_queue: Queue[
|
|
923
|
+
processing_queue: Queue[list[ProcessingJob] | None] = Queue(
|
|
937
924
|
maxsize=self.QUEUE_MAX_SIZE
|
|
938
925
|
)
|
|
939
926
|
self.processing_queues[follower_name] = processing_queue
|
|
@@ -1010,7 +997,7 @@ class NewMultiThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
1010
997
|
if thread.error:
|
|
1011
998
|
raise thread.error
|
|
1012
999
|
|
|
1013
|
-
def get(self, cls:
|
|
1000
|
+
def get(self, cls: type[TApplication]) -> TApplication:
|
|
1014
1001
|
app = self.apps[cls.name]
|
|
1015
1002
|
assert isinstance(app, cls)
|
|
1016
1003
|
return app
|
|
@@ -1111,7 +1098,7 @@ class ConvertingThread(threading.Thread):
|
|
|
1111
1098
|
def __init__(
|
|
1112
1099
|
self,
|
|
1113
1100
|
converting_queue: Queue[ConvertingJob],
|
|
1114
|
-
processing_queue: Queue[
|
|
1101
|
+
processing_queue: Queue[list[ProcessingJob] | None],
|
|
1115
1102
|
follower: Follower,
|
|
1116
1103
|
leader_name: str,
|
|
1117
1104
|
has_errored: threading.Event,
|
|
@@ -1181,7 +1168,7 @@ class ProcessingThread(threading.Thread):
|
|
|
1181
1168
|
|
|
1182
1169
|
def __init__(
|
|
1183
1170
|
self,
|
|
1184
|
-
processing_queue: Queue[
|
|
1171
|
+
processing_queue: Queue[list[ProcessingJob] | None],
|
|
1185
1172
|
follower: Follower,
|
|
1186
1173
|
has_errored: threading.Event,
|
|
1187
1174
|
):
|
|
@@ -1264,7 +1251,7 @@ class NotificationLogReader:
|
|
|
1264
1251
|
stop: int | None = None,
|
|
1265
1252
|
topics: Sequence[str] = (),
|
|
1266
1253
|
inclusive_of_start: bool = True,
|
|
1267
|
-
) -> Iterator[
|
|
1254
|
+
) -> Iterator[list[Notification]]:
|
|
1268
1255
|
"""
|
|
1269
1256
|
Returns a generator that yields lists of event notifications
|
|
1270
1257
|
from the reader's notification log, starting from given start
|
|
@@ -10,7 +10,7 @@ from decimal import Decimal
|
|
|
10
10
|
from threading import Event, get_ident
|
|
11
11
|
from time import sleep
|
|
12
12
|
from timeit import timeit
|
|
13
|
-
from typing import ClassVar
|
|
13
|
+
from typing import ClassVar
|
|
14
14
|
from unittest import TestCase
|
|
15
15
|
from uuid import UUID, uuid4
|
|
16
16
|
|
|
@@ -30,8 +30,8 @@ TIMEIT_FACTOR = int(os.environ.get("TEST_TIMEIT_FACTOR", default=10))
|
|
|
30
30
|
|
|
31
31
|
class ExampleApplicationTestCase(TestCase):
|
|
32
32
|
timeit_number: ClassVar[int] = TIMEIT_FACTOR
|
|
33
|
-
started_ats: ClassVar[
|
|
34
|
-
counts: ClassVar[
|
|
33
|
+
started_ats: ClassVar[dict[type[TestCase], datetime]] = {}
|
|
34
|
+
counts: ClassVar[dict[type[TestCase], int]] = {}
|
|
35
35
|
expected_factory_topic: str
|
|
36
36
|
|
|
37
37
|
def test_example_application(self):
|
|
@@ -10,7 +10,7 @@ from tempfile import NamedTemporaryFile
|
|
|
10
10
|
from threading import Event, Thread, get_ident
|
|
11
11
|
from time import sleep
|
|
12
12
|
from timeit import timeit
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import Any
|
|
14
14
|
from unittest import TestCase
|
|
15
15
|
from uuid import UUID, uuid4
|
|
16
16
|
|
|
@@ -429,11 +429,11 @@ class ApplicationRecorderTestCase(TestCase, ABC):
|
|
|
429
429
|
recorder = self.create_recorder()
|
|
430
430
|
|
|
431
431
|
errors_happened = Event()
|
|
432
|
-
errors:
|
|
432
|
+
errors: list[Exception] = []
|
|
433
433
|
|
|
434
434
|
counts = {}
|
|
435
|
-
threads:
|
|
436
|
-
durations:
|
|
435
|
+
threads: dict[int, int] = {}
|
|
436
|
+
durations: dict[int, float] = {}
|
|
437
437
|
|
|
438
438
|
num_writers = 10
|
|
439
439
|
num_writes_per_writer = 100
|
|
@@ -536,8 +536,8 @@ class ApplicationRecorderTestCase(TestCase, ABC):
|
|
|
536
536
|
errors_happened = Event()
|
|
537
537
|
|
|
538
538
|
counts = {}
|
|
539
|
-
threads:
|
|
540
|
-
durations:
|
|
539
|
+
threads: dict[int, int] = {}
|
|
540
|
+
durations: dict[int, float] = {}
|
|
541
541
|
|
|
542
542
|
# Match this to the batch page size in postgres insert for max throughput.
|
|
543
543
|
num_events = 500
|
|
@@ -642,7 +642,7 @@ class ApplicationRecorderTestCase(TestCase, ABC):
|
|
|
642
642
|
with recorder.subscribe(gt=max_notification_id1) as subscription:
|
|
643
643
|
|
|
644
644
|
# Receive events from the subscription.
|
|
645
|
-
notifications:
|
|
645
|
+
notifications: list[Notification] = []
|
|
646
646
|
for notification in subscription:
|
|
647
647
|
notifications.append(notification)
|
|
648
648
|
if len(notifications) == 2:
|
|
@@ -696,7 +696,7 @@ class ApplicationRecorderTestCase(TestCase, ABC):
|
|
|
696
696
|
with recorder.subscribe(gt=max_notification_id2) as subscription:
|
|
697
697
|
|
|
698
698
|
# Receive events from the subscription.
|
|
699
|
-
notifications:
|
|
699
|
+
notifications: list[Notification] = []
|
|
700
700
|
for notification in subscription:
|
|
701
701
|
notifications.append(notification)
|
|
702
702
|
if len(notifications) == 1:
|
|
@@ -1285,7 +1285,7 @@ class CustomType2:
|
|
|
1285
1285
|
return type(self) is type(other) and self.__dict__ == other.__dict__
|
|
1286
1286
|
|
|
1287
1287
|
|
|
1288
|
-
class
|
|
1288
|
+
class Mydict(dict):
|
|
1289
1289
|
def __repr__(self):
|
|
1290
1290
|
return f"{type(self).__name__}({super().__repr__()})"
|
|
1291
1291
|
|
|
@@ -1391,13 +1391,13 @@ class TranscoderTestCase(TestCase):
|
|
|
1391
1391
|
self.assertEqual(data, b"{}")
|
|
1392
1392
|
self.assertEqual(obj, self.transcoder.decode(data))
|
|
1393
1393
|
|
|
1394
|
-
#
|
|
1394
|
+
# dict with single key.
|
|
1395
1395
|
obj = {"a": 1}
|
|
1396
1396
|
data = self.transcoder.encode(obj)
|
|
1397
1397
|
self.assertEqual(data, b'{"a":1}')
|
|
1398
1398
|
self.assertEqual(obj, self.transcoder.decode(data))
|
|
1399
1399
|
|
|
1400
|
-
#
|
|
1400
|
+
# dict with many keys.
|
|
1401
1401
|
obj = {"a": 1, "b": 2}
|
|
1402
1402
|
data = self.transcoder.encode(obj)
|
|
1403
1403
|
self.assertEqual(data, b'{"a":1,"b":2}')
|
|
@@ -1444,7 +1444,7 @@ class TranscoderTestCase(TestCase):
|
|
|
1444
1444
|
self.assertEqual(obj, self.transcoder.decode(data))
|
|
1445
1445
|
|
|
1446
1446
|
def test_dict_subclass(self):
|
|
1447
|
-
my_dict =
|
|
1447
|
+
my_dict = Mydict({"a": 1})
|
|
1448
1448
|
data = self.transcoder.encode(my_dict)
|
|
1449
1449
|
self.assertEqual(b'{"_type_":"mydict","_data_":{"a":1}}', data)
|
|
1450
1450
|
copy = self.transcoder.decode(data)
|
eventsourcing/utils.py
CHANGED
|
@@ -2,25 +2,13 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
4
|
import sys
|
|
5
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
5
6
|
from functools import wraps
|
|
6
7
|
from inspect import isfunction
|
|
7
8
|
from random import random
|
|
8
9
|
from threading import Lock
|
|
9
10
|
from time import sleep
|
|
10
|
-
from typing import
|
|
11
|
-
TYPE_CHECKING,
|
|
12
|
-
Any,
|
|
13
|
-
Callable,
|
|
14
|
-
Dict,
|
|
15
|
-
Iterator,
|
|
16
|
-
List,
|
|
17
|
-
Mapping,
|
|
18
|
-
Sequence,
|
|
19
|
-
Type,
|
|
20
|
-
TypeVar,
|
|
21
|
-
no_type_check,
|
|
22
|
-
overload,
|
|
23
|
-
)
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar, no_type_check, overload
|
|
24
12
|
|
|
25
13
|
if TYPE_CHECKING:
|
|
26
14
|
from types import FunctionType, WrapperDescriptorType
|
|
@@ -32,8 +20,8 @@ class TopicError(Exception):
|
|
|
32
20
|
"""
|
|
33
21
|
|
|
34
22
|
|
|
35
|
-
_type_cache:
|
|
36
|
-
_topic_cache:
|
|
23
|
+
_type_cache: dict[type, str] = {}
|
|
24
|
+
_topic_cache: dict[str, Any] = {}
|
|
37
25
|
_topic_cache_lock = Lock()
|
|
38
26
|
|
|
39
27
|
|
|
@@ -138,7 +126,7 @@ def clear_topic_cache() -> None:
|
|
|
138
126
|
|
|
139
127
|
|
|
140
128
|
def retry(
|
|
141
|
-
exc:
|
|
129
|
+
exc: type[Exception] | Sequence[type[Exception]] = Exception,
|
|
142
130
|
max_attempts: int = 1,
|
|
143
131
|
wait: float = 0,
|
|
144
132
|
stall: float = 0,
|
|
@@ -223,7 +211,7 @@ def strtobool(val: str) -> bool:
|
|
|
223
211
|
raise ValueError(msg)
|
|
224
212
|
|
|
225
213
|
|
|
226
|
-
def reversed_keys(d:
|
|
214
|
+
def reversed_keys(d: dict[Any, Any]) -> Iterator[Any]:
|
|
227
215
|
return reversed(d.keys())
|
|
228
216
|
|
|
229
217
|
|
|
@@ -237,7 +225,7 @@ EnvType = Mapping[str, str]
|
|
|
237
225
|
T = TypeVar("T")
|
|
238
226
|
|
|
239
227
|
|
|
240
|
-
class Environment(
|
|
228
|
+
class Environment(dict[str, str]):
|
|
241
229
|
def __init__(self, name: str = "", env: EnvType | None = None):
|
|
242
230
|
super().__init__(env or {})
|
|
243
231
|
self.name = name
|
|
@@ -255,7 +243,7 @@ class Environment(Dict[str, str]):
|
|
|
255
243
|
return value
|
|
256
244
|
return default
|
|
257
245
|
|
|
258
|
-
def create_keys(self, key: str) ->
|
|
246
|
+
def create_keys(self, key: str) -> list[str]:
|
|
259
247
|
keys = []
|
|
260
248
|
if self.name:
|
|
261
249
|
keys.append(self.name.upper() + "_" + key)
|