eventsourcing 9.4.0a8__tar.gz → 9.4.0b1__tar.gz
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-9.4.0a8 → eventsourcing-9.4.0b1}/PKG-INFO +2 -2
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/application.py +19 -23
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/cipher.py +3 -1
- eventsourcing-9.4.0b1/eventsourcing/dispatch.py +79 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/domain.py +45 -21
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/interface.py +1 -1
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/persistence.py +8 -4
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/postgres.py +136 -97
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/projection.py +62 -9
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/system.py +4 -7
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/tests/domain.py +6 -6
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/tests/persistence.py +8 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/tests/postgres_utils.py +5 -1
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/utils.py +12 -7
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/pyproject.toml +13 -3
- eventsourcing-9.4.0a8/eventsourcing/dispatch.py +0 -38
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/AUTHORS +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/LICENSE +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/README.md +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/__init__.py +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/compressor.py +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/cryptography.py +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/popo.py +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/py.typed +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/sqlite.py +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/tests/__init__.py +0 -0
- {eventsourcing-9.4.0a8 → eventsourcing-9.4.0b1}/eventsourcing/tests/application.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: eventsourcing
|
|
3
|
-
Version: 9.4.
|
|
3
|
+
Version: 9.4.0b1
|
|
4
4
|
Summary: Event sourcing in Python
|
|
5
5
|
License: BSD 3-Clause
|
|
6
6
|
Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
|
|
7
7
|
Author: John Bywater
|
|
8
8
|
Author-email: john.bywater@appropriatesoftware.net
|
|
9
9
|
Requires-Python: >=3.9, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
|
|
10
|
-
Classifier: Development Status ::
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Intended Audience :: Education
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import os
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from collections.abc import Iterable, Iterator, Sequence
|
|
@@ -26,6 +27,7 @@ from eventsourcing.domain import (
|
|
|
26
27
|
DomainEventProtocol,
|
|
27
28
|
EventSourcingError,
|
|
28
29
|
MutableOrImmutableAggregate,
|
|
30
|
+
SDomainEvent,
|
|
29
31
|
Snapshot,
|
|
30
32
|
SnapshotProtocol,
|
|
31
33
|
TDomainEvent,
|
|
@@ -352,19 +354,18 @@ class Repository:
|
|
|
352
354
|
return aggregate
|
|
353
355
|
|
|
354
356
|
def _use_fastforward_lock(self, aggregate_id: UUID) -> Lock:
|
|
357
|
+
lock: Lock | None = None
|
|
355
358
|
with self._fastforward_locks_lock:
|
|
356
|
-
|
|
359
|
+
num_users = 0
|
|
360
|
+
with contextlib.suppress(KeyError):
|
|
357
361
|
lock, num_users = self._fastforward_locks_inuse[aggregate_id]
|
|
358
|
-
|
|
359
|
-
|
|
362
|
+
if lock is None:
|
|
363
|
+
with contextlib.suppress(KeyError):
|
|
360
364
|
lock = self._fastforward_locks_cache.get(aggregate_id, evict=True)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
finally:
|
|
366
|
-
num_users += 1
|
|
367
|
-
self._fastforward_locks_inuse[aggregate_id] = (lock, num_users)
|
|
365
|
+
if lock is None:
|
|
366
|
+
lock = Lock()
|
|
367
|
+
num_users += 1
|
|
368
|
+
self._fastforward_locks_inuse[aggregate_id] = (lock, num_users)
|
|
368
369
|
return lock
|
|
369
370
|
|
|
370
371
|
def _disuse_fastforward_lock(self, aggregate_id: UUID) -> None:
|
|
@@ -610,12 +611,10 @@ class Application:
|
|
|
610
611
|
name = "Application"
|
|
611
612
|
env: ClassVar[dict[str, str]] = {}
|
|
612
613
|
is_snapshotting_enabled: bool = False
|
|
613
|
-
snapshotting_intervals: ClassVar[
|
|
614
|
-
dict[type[MutableOrImmutableAggregate], int] | None
|
|
615
|
-
] = None
|
|
614
|
+
snapshotting_intervals: ClassVar[dict[type[MutableOrImmutableAggregate], int]] = {}
|
|
616
615
|
snapshotting_projectors: ClassVar[
|
|
617
|
-
dict[type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]]
|
|
618
|
-
] =
|
|
616
|
+
dict[type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]]
|
|
617
|
+
] = {}
|
|
619
618
|
snapshot_class: type[SnapshotProtocol] = Snapshot
|
|
620
619
|
log_section_size = 10
|
|
621
620
|
notify_topics: Sequence[str] = []
|
|
@@ -817,12 +816,9 @@ class Application:
|
|
|
817
816
|
continue
|
|
818
817
|
interval = self.snapshotting_intervals.get(type(aggregate))
|
|
819
818
|
if interval is not None and event.originator_version % interval == 0:
|
|
820
|
-
|
|
821
|
-
self.snapshotting_projectors
|
|
822
|
-
and type(aggregate) in self.snapshotting_projectors
|
|
823
|
-
):
|
|
819
|
+
try:
|
|
824
820
|
projector_func = self.snapshotting_projectors[type(aggregate)]
|
|
825
|
-
|
|
821
|
+
except KeyError:
|
|
826
822
|
projector_func = project_aggregate
|
|
827
823
|
if projector_func is project_aggregate and not isinstance(
|
|
828
824
|
event, CanMutateProtocol
|
|
@@ -947,10 +943,10 @@ class EventSourcedLog(Generic[TDomainEvent]):
|
|
|
947
943
|
|
|
948
944
|
def _trigger_event(
|
|
949
945
|
self,
|
|
950
|
-
logged_cls: type[
|
|
946
|
+
logged_cls: type[SDomainEvent],
|
|
951
947
|
next_originator_version: int | None = None,
|
|
952
948
|
**kwargs: Any,
|
|
953
|
-
) ->
|
|
949
|
+
) -> SDomainEvent:
|
|
954
950
|
"""
|
|
955
951
|
Constructs and returns a new log event.
|
|
956
952
|
"""
|
|
@@ -961,7 +957,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
|
|
|
961
957
|
else:
|
|
962
958
|
next_originator_version = last_logged.originator_version + 1
|
|
963
959
|
|
|
964
|
-
return logged_cls(
|
|
960
|
+
return logged_cls(
|
|
965
961
|
originator_id=self.originator_id,
|
|
966
962
|
originator_version=next_originator_version,
|
|
967
963
|
timestamp=datetime_now_with_tzinfo(),
|
|
@@ -5,7 +5,9 @@ from base64 import b64decode, b64encode
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from Crypto.Cipher import AES
|
|
8
|
-
from Crypto.Cipher._mode_gcm import
|
|
8
|
+
from Crypto.Cipher._mode_gcm import (
|
|
9
|
+
GcmMode, # pyright: ignore [reportPrivateImportUsage]
|
|
10
|
+
)
|
|
9
11
|
from Crypto.Cipher.AES import key_size
|
|
10
12
|
|
|
11
13
|
from eventsourcing.persistence import Cipher
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, cast, overload
|
|
5
|
+
|
|
6
|
+
_T = TypeVar("_T")
|
|
7
|
+
_S = TypeVar("_S")
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
|
|
11
|
+
class _singledispatchmethod(functools.singledispatchmethod[_T]): # noqa: N801
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
else:
|
|
15
|
+
|
|
16
|
+
class _singledispatchmethod( # noqa: N801
|
|
17
|
+
functools.singledispatchmethod, Generic[_T]
|
|
18
|
+
):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class singledispatchmethod(_singledispatchmethod[_T]): # noqa: N801
|
|
23
|
+
def __init__(self, func: Callable[..., _T]) -> None:
|
|
24
|
+
super().__init__(func)
|
|
25
|
+
self.deferred_registrations: list[
|
|
26
|
+
tuple[type[Any] | Callable[..., _T], Callable[..., _T] | None]
|
|
27
|
+
] = []
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def register(
|
|
31
|
+
self, cls: type[Any], method: None = None
|
|
32
|
+
) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... # pragma: no cover
|
|
33
|
+
@overload
|
|
34
|
+
def register(
|
|
35
|
+
self, cls: Callable[..., _T], method: None = None
|
|
36
|
+
) -> Callable[..., _T]: ... # pragma: no cover
|
|
37
|
+
|
|
38
|
+
@overload
|
|
39
|
+
def register(
|
|
40
|
+
self, cls: type[Any], method: Callable[..., _T]
|
|
41
|
+
) -> Callable[..., _T]: ... # pragma: no cover
|
|
42
|
+
|
|
43
|
+
def register(
|
|
44
|
+
self,
|
|
45
|
+
cls: type[Any] | Callable[..., _T],
|
|
46
|
+
method: Callable[..., _T] | None = None,
|
|
47
|
+
) -> Callable[[Callable[..., _T]], Callable[..., _T]] | Callable[..., _T]:
|
|
48
|
+
"""generic_method.register(cls, func) -> func
|
|
49
|
+
|
|
50
|
+
Registers a new implementation for the given *cls* on a *generic_method*.
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(cls, (classmethod, staticmethod)):
|
|
53
|
+
first_annotation = {}
|
|
54
|
+
for k, v in cls.__func__.__annotations__.items():
|
|
55
|
+
first_annotation[k] = v
|
|
56
|
+
break
|
|
57
|
+
cls.__annotations__ = first_annotation
|
|
58
|
+
|
|
59
|
+
# for globals in typing.get_type_hints() in Python 3.8 and 3.9
|
|
60
|
+
if not hasattr(cls, "__wrapped__"):
|
|
61
|
+
cls.__dict__["__wrapped__"] = cls.__func__
|
|
62
|
+
# cls.__wrapped__ = cls.__func__
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
return self.dispatcher.register(cast(type[Any], cls), func=method)
|
|
66
|
+
except NameError:
|
|
67
|
+
self.deferred_registrations.append(
|
|
68
|
+
(cls, method) # pyright: ignore [reportArgumentType]
|
|
69
|
+
)
|
|
70
|
+
# TODO: Fix this....
|
|
71
|
+
return method or cls # pyright: ignore [reportReturnType]
|
|
72
|
+
|
|
73
|
+
def __get__(self, obj: _S, cls: type[_S] | None = None) -> Callable[..., _T]:
|
|
74
|
+
for registered_cls, registered_method in self.deferred_registrations:
|
|
75
|
+
self.dispatcher.register(
|
|
76
|
+
cast(type[Any], registered_cls), func=registered_method
|
|
77
|
+
)
|
|
78
|
+
self.deferred_registrations = []
|
|
79
|
+
return super().__get__(obj, cls=cls)
|
|
@@ -88,20 +88,26 @@ class DomainEventProtocol(Protocol):
|
|
|
88
88
|
kinds of domain event classes, such as Pydantic classes.
|
|
89
89
|
"""
|
|
90
90
|
|
|
91
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
92
|
+
pass # pragma: no cover
|
|
93
|
+
|
|
91
94
|
@property
|
|
92
95
|
def originator_id(self) -> UUID:
|
|
93
96
|
"""
|
|
94
97
|
UUID identifying an aggregate to which the event belongs.
|
|
95
98
|
"""
|
|
99
|
+
raise NotImplementedError # pragma: no cover
|
|
96
100
|
|
|
97
101
|
@property
|
|
98
102
|
def originator_version(self) -> int:
|
|
99
103
|
"""
|
|
100
104
|
Integer identifying the version of the aggregate when the event occurred.
|
|
101
105
|
"""
|
|
106
|
+
raise NotImplementedError # pragma: no cover
|
|
102
107
|
|
|
103
108
|
|
|
104
109
|
TDomainEvent = TypeVar("TDomainEvent", bound=DomainEventProtocol)
|
|
110
|
+
SDomainEvent = TypeVar("SDomainEvent", bound=DomainEventProtocol)
|
|
105
111
|
|
|
106
112
|
|
|
107
113
|
class MutableAggregateProtocol(Protocol):
|
|
@@ -120,18 +126,21 @@ class MutableAggregateProtocol(Protocol):
|
|
|
120
126
|
"""
|
|
121
127
|
Mutable aggregates have a read-only ID that is a UUID.
|
|
122
128
|
"""
|
|
129
|
+
raise NotImplementedError # pragma: no cover
|
|
123
130
|
|
|
124
131
|
@property
|
|
125
132
|
def version(self) -> int:
|
|
126
133
|
"""
|
|
127
134
|
Mutable aggregates have a read-write version that is an int.
|
|
128
135
|
"""
|
|
136
|
+
raise NotImplementedError # pragma: no cover
|
|
129
137
|
|
|
130
138
|
@version.setter
|
|
131
139
|
def version(self, value: int) -> None:
|
|
132
140
|
"""
|
|
133
141
|
Mutable aggregates have a read-write version that is an int.
|
|
134
142
|
"""
|
|
143
|
+
raise NotImplementedError # pragma: no cover
|
|
135
144
|
|
|
136
145
|
|
|
137
146
|
class ImmutableAggregateProtocol(Protocol):
|
|
@@ -150,12 +159,14 @@ class ImmutableAggregateProtocol(Protocol):
|
|
|
150
159
|
"""
|
|
151
160
|
Immutable aggregates have a read-only ID that is a UUID.
|
|
152
161
|
"""
|
|
162
|
+
raise NotImplementedError # pragma: no cover
|
|
153
163
|
|
|
154
164
|
@property
|
|
155
165
|
def version(self) -> int:
|
|
156
166
|
"""
|
|
157
167
|
Immutable aggregates have a read-only version that is an int.
|
|
158
168
|
"""
|
|
169
|
+
raise NotImplementedError # pragma: no cover
|
|
159
170
|
|
|
160
171
|
|
|
161
172
|
MutableOrImmutableAggregate = Union[
|
|
@@ -180,6 +191,7 @@ class CollectEventsProtocol(Protocol):
|
|
|
180
191
|
"""
|
|
181
192
|
Returns a sequence of events.
|
|
182
193
|
"""
|
|
194
|
+
raise NotImplementedError # pragma: no cover
|
|
183
195
|
|
|
184
196
|
|
|
185
197
|
@runtime_checkable
|
|
@@ -233,7 +245,7 @@ class CanCreateTimestamp:
|
|
|
233
245
|
return datetime_now_with_tzinfo()
|
|
234
246
|
|
|
235
247
|
|
|
236
|
-
TAggregate = TypeVar("TAggregate", bound="
|
|
248
|
+
TAggregate = TypeVar("TAggregate", bound="BaseAggregate")
|
|
237
249
|
|
|
238
250
|
|
|
239
251
|
class HasOriginatorIDVersion:
|
|
@@ -300,7 +312,7 @@ class CanMutateAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
|
|
|
300
312
|
# Return the mutated aggregate.
|
|
301
313
|
return aggregate
|
|
302
314
|
|
|
303
|
-
def apply(self, aggregate:
|
|
315
|
+
def apply(self, aggregate: Any) -> None:
|
|
304
316
|
"""
|
|
305
317
|
Applies the domain event to its aggregate.
|
|
306
318
|
|
|
@@ -934,9 +946,9 @@ def _raise_missing_names_type_error(missing_names: list[str], msg: str) -> None:
|
|
|
934
946
|
raise TypeError(msg)
|
|
935
947
|
|
|
936
948
|
|
|
937
|
-
_annotations_mention_id: set[type[
|
|
938
|
-
_init_mentions_id: set[type[
|
|
939
|
-
_create_id_param_names: dict[type[
|
|
949
|
+
_annotations_mention_id: set[type[BaseAggregate]] = set()
|
|
950
|
+
_init_mentions_id: set[type[BaseAggregate]] = set()
|
|
951
|
+
_create_id_param_names: dict[type[BaseAggregate], list[str]] = defaultdict(list)
|
|
940
952
|
|
|
941
953
|
|
|
942
954
|
class MetaAggregate(EventsourcingType, Generic[TAggregate], type):
|
|
@@ -1011,19 +1023,13 @@ class MetaAggregate(EventsourcingType, Generic[TAggregate], type):
|
|
|
1011
1023
|
_created_event_class: type[CanInitAggregate]
|
|
1012
1024
|
|
|
1013
1025
|
|
|
1014
|
-
class
|
|
1026
|
+
class BaseAggregate(metaclass=MetaAggregate):
|
|
1015
1027
|
"""
|
|
1016
1028
|
Base class for aggregates.
|
|
1017
1029
|
"""
|
|
1018
1030
|
|
|
1019
1031
|
INITIAL_VERSION = 1
|
|
1020
1032
|
|
|
1021
|
-
class Event(AggregateEvent):
|
|
1022
|
-
pass
|
|
1023
|
-
|
|
1024
|
-
class Created(Event, AggregateCreated):
|
|
1025
|
-
pass
|
|
1026
|
-
|
|
1027
1033
|
@staticmethod
|
|
1028
1034
|
def create_id(*_: Any, **__: Any) -> UUID:
|
|
1029
1035
|
"""
|
|
@@ -1081,7 +1087,7 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1081
1087
|
|
|
1082
1088
|
assert agg is not None
|
|
1083
1089
|
# Append the domain event to pending list.
|
|
1084
|
-
agg.
|
|
1090
|
+
agg._pending_events.append(created_event)
|
|
1085
1091
|
# Return the aggregate.
|
|
1086
1092
|
return agg
|
|
1087
1093
|
|
|
@@ -1197,7 +1203,7 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1197
1203
|
return f"{type(self).__name__}({', '.join(attrs)})"
|
|
1198
1204
|
|
|
1199
1205
|
def __init_subclass__(
|
|
1200
|
-
cls: type[
|
|
1206
|
+
cls: type[BaseAggregate], *, created_event_name: str | None = None
|
|
1201
1207
|
) -> None:
|
|
1202
1208
|
"""
|
|
1203
1209
|
Initialises aggregate subclass by defining __init__ method and event classes.
|
|
@@ -1211,8 +1217,10 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1211
1217
|
except KeyError:
|
|
1212
1218
|
pass
|
|
1213
1219
|
|
|
1214
|
-
if
|
|
1215
|
-
|
|
1220
|
+
if (
|
|
1221
|
+
class_annotations
|
|
1222
|
+
or cls in _annotations_mention_id
|
|
1223
|
+
or any(dataclasses.is_dataclass(base) for base in cls.__bases__)
|
|
1216
1224
|
):
|
|
1217
1225
|
dataclasses.dataclass(eq=False, repr=False)(cls)
|
|
1218
1226
|
|
|
@@ -1223,7 +1231,9 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1223
1231
|
base_event_cls = cls.__dict__[base_event_name]
|
|
1224
1232
|
except KeyError:
|
|
1225
1233
|
base_event_cls = cls._define_event_class(
|
|
1226
|
-
base_event_name,
|
|
1234
|
+
name=base_event_name,
|
|
1235
|
+
bases=(getattr(cls, base_event_name, AggregateEvent),),
|
|
1236
|
+
apply_method=None,
|
|
1227
1237
|
)
|
|
1228
1238
|
setattr(cls, base_event_name, base_event_cls)
|
|
1229
1239
|
|
|
@@ -1482,9 +1492,12 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1482
1492
|
setattr(cls, name, sub_class)
|
|
1483
1493
|
|
|
1484
1494
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1495
|
+
class Aggregate(BaseAggregate):
|
|
1496
|
+
class Event(AggregateEvent):
|
|
1497
|
+
pass
|
|
1498
|
+
|
|
1499
|
+
class Created(Event, AggregateCreated):
|
|
1500
|
+
pass
|
|
1488
1501
|
|
|
1489
1502
|
|
|
1490
1503
|
@overload
|
|
@@ -1578,6 +1591,7 @@ class SnapshotProtocol(DomainEventProtocol, Protocol):
|
|
|
1578
1591
|
"""
|
|
1579
1592
|
Snapshots have a read-only 'state'.
|
|
1580
1593
|
"""
|
|
1594
|
+
raise NotImplementedError # pragma: no cover
|
|
1581
1595
|
|
|
1582
1596
|
# TODO: Improve on this 'Any'.
|
|
1583
1597
|
@classmethod
|
|
@@ -1594,6 +1608,16 @@ class CanSnapshotAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
|
|
|
1594
1608
|
topic: str
|
|
1595
1609
|
state: Any
|
|
1596
1610
|
|
|
1611
|
+
def __init__(
|
|
1612
|
+
self,
|
|
1613
|
+
originator_id: UUID,
|
|
1614
|
+
originator_version: int,
|
|
1615
|
+
timestamp: datetime,
|
|
1616
|
+
topic: str,
|
|
1617
|
+
state: Any,
|
|
1618
|
+
) -> None:
|
|
1619
|
+
raise NotImplementedError # pragma: no cover
|
|
1620
|
+
|
|
1597
1621
|
@classmethod
|
|
1598
1622
|
def take(
|
|
1599
1623
|
cls: type[TCanSnapshotAggregate],
|
|
@@ -1610,7 +1634,7 @@ class CanSnapshotAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
|
|
|
1610
1634
|
aggregate_state.pop("_id")
|
|
1611
1635
|
aggregate_state.pop("_version")
|
|
1612
1636
|
aggregate_state.pop("_pending_events")
|
|
1613
|
-
return cls(
|
|
1637
|
+
return cls(
|
|
1614
1638
|
originator_id=aggregate.id,
|
|
1615
1639
|
originator_version=aggregate.version,
|
|
1616
1640
|
timestamp=cls.create_timestamp(),
|
|
@@ -517,10 +517,10 @@ class TrackingRecorder(Recorder, ABC):
|
|
|
517
517
|
interrupt: Event | None = None,
|
|
518
518
|
) -> None:
|
|
519
519
|
"""
|
|
520
|
-
Block until a tracking object with the given application name and
|
|
521
|
-
notification ID has been recorded.
|
|
520
|
+
Block until a tracking object with the given application name and a
|
|
521
|
+
notification ID greater than equal to the given value has been recorded.
|
|
522
522
|
|
|
523
|
-
Polls
|
|
523
|
+
Polls max_tracking_id() with exponential backoff until the timeout
|
|
524
524
|
is reached, or until the optional interrupt event is set.
|
|
525
525
|
|
|
526
526
|
The timeout argument should be a floating point number specifying a
|
|
@@ -534,7 +534,10 @@ class TrackingRecorder(Recorder, ABC):
|
|
|
534
534
|
deadline = monotonic() + timeout
|
|
535
535
|
delay_ms = 1.0
|
|
536
536
|
while True:
|
|
537
|
-
|
|
537
|
+
max_tracking_id = self.max_tracking_id(application_name)
|
|
538
|
+
if notification_id is None or (
|
|
539
|
+
max_tracking_id is not None and max_tracking_id >= notification_id
|
|
540
|
+
):
|
|
538
541
|
break
|
|
539
542
|
if interrupt:
|
|
540
543
|
if interrupt.wait(timeout=delay_ms / 1000):
|
|
@@ -751,6 +754,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
751
754
|
mapper_topic = self.env.get(self.MAPPER_TOPIC)
|
|
752
755
|
mapper_class = resolve_topic(mapper_topic) if mapper_topic else Mapper
|
|
753
756
|
|
|
757
|
+
assert isinstance(mapper_class, type) and issubclass(mapper_class, Mapper)
|
|
754
758
|
return mapper_class(
|
|
755
759
|
transcoder=transcoder or self.transcoder(),
|
|
756
760
|
cipher=self.cipher(),
|