eventsourcing 9.2.22__py3-none-any.whl → 9.3.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of eventsourcing might be problematic. Click here for more details.
- eventsourcing/__init__.py +1 -1
- eventsourcing/application.py +106 -135
- eventsourcing/cipher.py +15 -12
- eventsourcing/dispatch.py +31 -91
- eventsourcing/domain.py +138 -143
- eventsourcing/examples/__init__.py +0 -0
- eventsourcing/examples/aggregate1/__init__.py +0 -0
- eventsourcing/examples/aggregate1/application.py +27 -0
- eventsourcing/examples/aggregate1/domainmodel.py +16 -0
- eventsourcing/examples/aggregate1/test_application.py +37 -0
- eventsourcing/examples/aggregate2/__init__.py +0 -0
- eventsourcing/examples/aggregate2/application.py +27 -0
- eventsourcing/examples/aggregate2/domainmodel.py +22 -0
- eventsourcing/examples/aggregate2/test_application.py +37 -0
- eventsourcing/examples/aggregate3/__init__.py +0 -0
- eventsourcing/examples/aggregate3/application.py +27 -0
- eventsourcing/examples/aggregate3/domainmodel.py +38 -0
- eventsourcing/examples/aggregate3/test_application.py +37 -0
- eventsourcing/examples/aggregate4/__init__.py +0 -0
- eventsourcing/examples/aggregate4/application.py +27 -0
- eventsourcing/examples/aggregate4/domainmodel.py +128 -0
- eventsourcing/examples/aggregate4/test_application.py +38 -0
- eventsourcing/examples/aggregate5/__init__.py +0 -0
- eventsourcing/examples/aggregate5/application.py +27 -0
- eventsourcing/examples/aggregate5/domainmodel.py +131 -0
- eventsourcing/examples/aggregate5/test_application.py +38 -0
- eventsourcing/examples/aggregate6/__init__.py +0 -0
- eventsourcing/examples/aggregate6/application.py +30 -0
- eventsourcing/examples/aggregate6/domainmodel.py +123 -0
- eventsourcing/examples/aggregate6/test_application.py +38 -0
- eventsourcing/examples/aggregate6a/__init__.py +0 -0
- eventsourcing/examples/aggregate6a/application.py +40 -0
- eventsourcing/examples/aggregate6a/domainmodel.py +149 -0
- eventsourcing/examples/aggregate6a/test_application.py +45 -0
- eventsourcing/examples/aggregate7/__init__.py +0 -0
- eventsourcing/examples/aggregate7/application.py +48 -0
- eventsourcing/examples/aggregate7/domainmodel.py +144 -0
- eventsourcing/examples/aggregate7/persistence.py +57 -0
- eventsourcing/examples/aggregate7/test_application.py +38 -0
- eventsourcing/examples/aggregate7/test_compression_and_encryption.py +45 -0
- eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +67 -0
- eventsourcing/examples/aggregate7a/__init__.py +0 -0
- eventsourcing/examples/aggregate7a/application.py +56 -0
- eventsourcing/examples/aggregate7a/domainmodel.py +170 -0
- eventsourcing/examples/aggregate7a/test_application.py +46 -0
- eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +45 -0
- eventsourcing/examples/aggregate8/__init__.py +0 -0
- eventsourcing/examples/aggregate8/application.py +47 -0
- eventsourcing/examples/aggregate8/domainmodel.py +65 -0
- eventsourcing/examples/aggregate8/persistence.py +57 -0
- eventsourcing/examples/aggregate8/test_application.py +37 -0
- eventsourcing/examples/aggregate8/test_compression_and_encryption.py +44 -0
- eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +38 -0
- eventsourcing/examples/bankaccounts/__init__.py +0 -0
- eventsourcing/examples/bankaccounts/application.py +70 -0
- eventsourcing/examples/bankaccounts/domainmodel.py +56 -0
- eventsourcing/examples/bankaccounts/test.py +173 -0
- eventsourcing/examples/cargoshipping/__init__.py +0 -0
- eventsourcing/examples/cargoshipping/application.py +126 -0
- eventsourcing/examples/cargoshipping/domainmodel.py +330 -0
- eventsourcing/examples/cargoshipping/interface.py +143 -0
- eventsourcing/examples/cargoshipping/test.py +231 -0
- eventsourcing/examples/contentmanagement/__init__.py +0 -0
- eventsourcing/examples/contentmanagement/application.py +118 -0
- eventsourcing/examples/contentmanagement/domainmodel.py +69 -0
- eventsourcing/examples/contentmanagement/test.py +180 -0
- eventsourcing/examples/contentmanagement/utils.py +26 -0
- eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
- eventsourcing/examples/contentmanagementsystem/application.py +54 -0
- eventsourcing/examples/contentmanagementsystem/postgres.py +17 -0
- eventsourcing/examples/contentmanagementsystem/sqlite.py +17 -0
- eventsourcing/examples/contentmanagementsystem/system.py +14 -0
- eventsourcing/examples/contentmanagementsystem/test_system.py +174 -0
- eventsourcing/examples/searchablecontent/__init__.py +0 -0
- eventsourcing/examples/searchablecontent/application.py +45 -0
- eventsourcing/examples/searchablecontent/persistence.py +23 -0
- eventsourcing/examples/searchablecontent/postgres.py +118 -0
- eventsourcing/examples/searchablecontent/sqlite.py +136 -0
- eventsourcing/examples/searchablecontent/test_application.py +111 -0
- eventsourcing/examples/searchablecontent/test_recorder.py +69 -0
- eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
- eventsourcing/examples/searchabletimestamps/application.py +32 -0
- eventsourcing/examples/searchabletimestamps/persistence.py +20 -0
- eventsourcing/examples/searchabletimestamps/postgres.py +110 -0
- eventsourcing/examples/searchabletimestamps/sqlite.py +99 -0
- eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +91 -0
- eventsourcing/examples/test_invoice.py +176 -0
- eventsourcing/examples/test_parking_lot.py +206 -0
- eventsourcing/interface.py +2 -2
- eventsourcing/persistence.py +85 -81
- eventsourcing/popo.py +30 -31
- eventsourcing/postgres.py +361 -578
- eventsourcing/sqlite.py +91 -99
- eventsourcing/system.py +42 -57
- eventsourcing/tests/application.py +20 -32
- eventsourcing/tests/application_tests/__init__.py +0 -0
- eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +55 -0
- eventsourcing/tests/application_tests/test_application_with_popo.py +22 -0
- eventsourcing/tests/application_tests/test_application_with_postgres.py +75 -0
- eventsourcing/tests/application_tests/test_application_with_sqlite.py +72 -0
- eventsourcing/tests/application_tests/test_cache.py +134 -0
- eventsourcing/tests/application_tests/test_event_sourced_log.py +162 -0
- eventsourcing/tests/application_tests/test_notificationlog.py +232 -0
- eventsourcing/tests/application_tests/test_notificationlogreader.py +126 -0
- eventsourcing/tests/application_tests/test_processapplication.py +110 -0
- eventsourcing/tests/application_tests/test_processingpolicy.py +109 -0
- eventsourcing/tests/application_tests/test_repository.py +504 -0
- eventsourcing/tests/application_tests/test_snapshotting.py +68 -0
- eventsourcing/tests/application_tests/test_upcasting.py +459 -0
- eventsourcing/tests/docs_tests/__init__.py +0 -0
- eventsourcing/tests/docs_tests/test_docs.py +293 -0
- eventsourcing/tests/domain.py +1 -1
- eventsourcing/tests/domain_tests/__init__.py +0 -0
- eventsourcing/tests/domain_tests/test_aggregate.py +1159 -0
- eventsourcing/tests/domain_tests/test_aggregate_decorators.py +1604 -0
- eventsourcing/tests/domain_tests/test_domainevent.py +80 -0
- eventsourcing/tests/interface_tests/__init__.py +0 -0
- eventsourcing/tests/interface_tests/test_remotenotificationlog.py +258 -0
- eventsourcing/tests/persistence.py +49 -50
- eventsourcing/tests/persistence_tests/__init__.py +0 -0
- eventsourcing/tests/persistence_tests/test_aes.py +93 -0
- eventsourcing/tests/persistence_tests/test_connection_pool.py +722 -0
- eventsourcing/tests/persistence_tests/test_eventstore.py +72 -0
- eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +21 -0
- eventsourcing/tests/persistence_tests/test_mapper.py +113 -0
- eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +69 -0
- eventsourcing/tests/persistence_tests/test_popo.py +124 -0
- eventsourcing/tests/persistence_tests/test_postgres.py +1121 -0
- eventsourcing/tests/persistence_tests/test_sqlite.py +348 -0
- eventsourcing/tests/persistence_tests/test_transcoder.py +44 -0
- eventsourcing/tests/postgres_utils.py +7 -7
- eventsourcing/tests/system_tests/__init__.py +0 -0
- eventsourcing/tests/system_tests/test_runner.py +935 -0
- eventsourcing/tests/system_tests/test_system.py +287 -0
- eventsourcing/tests/utils_tests/__init__.py +0 -0
- eventsourcing/tests/utils_tests/test_utils.py +226 -0
- eventsourcing/utils.py +47 -50
- {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0a1.dist-info}/METADATA +28 -80
- eventsourcing-9.3.0a1.dist-info/RECORD +144 -0
- {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0a1.dist-info}/WHEEL +1 -2
- eventsourcing-9.2.22.dist-info/AUTHORS +0 -10
- eventsourcing-9.2.22.dist-info/RECORD +0 -25
- eventsourcing-9.2.22.dist-info/top_level.txt +0 -1
- {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0a1.dist-info}/LICENSE +0 -0
eventsourcing/domain.py
CHANGED
|
@@ -2,34 +2,28 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import os
|
|
5
|
-
import sys
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from datetime import datetime, tzinfo
|
|
8
7
|
from functools import lru_cache
|
|
9
8
|
from types import FunctionType, WrapperDescriptorType
|
|
10
9
|
from typing import (
|
|
10
|
+
TYPE_CHECKING,
|
|
11
11
|
Any,
|
|
12
12
|
Callable,
|
|
13
13
|
Dict,
|
|
14
14
|
Generic,
|
|
15
15
|
Iterable,
|
|
16
16
|
List,
|
|
17
|
-
|
|
17
|
+
Protocol,
|
|
18
18
|
Sequence,
|
|
19
|
-
Set,
|
|
20
19
|
Tuple,
|
|
21
20
|
Type,
|
|
22
21
|
TypeVar,
|
|
23
22
|
Union,
|
|
24
23
|
cast,
|
|
25
24
|
overload,
|
|
25
|
+
runtime_checkable,
|
|
26
26
|
)
|
|
27
|
-
|
|
28
|
-
if sys.version_info >= (3, 8): # pragma: no cover
|
|
29
|
-
from typing import Protocol, runtime_checkable
|
|
30
|
-
else: # pragma: no cover
|
|
31
|
-
from typing_extensions import Protocol, runtime_checkable
|
|
32
|
-
|
|
33
27
|
from uuid import UUID, uuid4
|
|
34
28
|
|
|
35
29
|
from eventsourcing.utils import get_method_name, get_topic, resolve_topic
|
|
@@ -150,8 +144,8 @@ class CanMutateProtocol(DomainEventProtocol, Protocol[TMutableOrImmutableAggrega
|
|
|
150
144
|
"""
|
|
151
145
|
|
|
152
146
|
def mutate(
|
|
153
|
-
self, aggregate:
|
|
154
|
-
) ->
|
|
147
|
+
self, aggregate: TMutableOrImmutableAggregate | None
|
|
148
|
+
) -> TMutableOrImmutableAggregate | None:
|
|
155
149
|
"""
|
|
156
150
|
Evolves the state of the given aggregate, either by
|
|
157
151
|
returning the given aggregate instance with modified attributes
|
|
@@ -203,7 +197,7 @@ class CanMutateAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
|
|
|
203
197
|
timestamp: datetime
|
|
204
198
|
"""Timezone-aware :class:`datetime` object representing when an event occurred."""
|
|
205
199
|
|
|
206
|
-
def mutate(self, aggregate:
|
|
200
|
+
def mutate(self, aggregate: TAggregate | None) -> TAggregate | None:
|
|
207
201
|
"""
|
|
208
202
|
Validates and adjusted the attributes of the given ``aggregate`` argument. The
|
|
209
203
|
argument is typed as ``Optional`` but the value is expected to be not ``None``.
|
|
@@ -266,7 +260,7 @@ class CanInitAggregate(CanMutateAggregate):
|
|
|
266
260
|
originator_topic: str
|
|
267
261
|
"""String describing the path to an aggregate class."""
|
|
268
262
|
|
|
269
|
-
def mutate(self, aggregate:
|
|
263
|
+
def mutate(self, aggregate: TAggregate | None) -> TAggregate | None:
|
|
270
264
|
"""
|
|
271
265
|
Constructs an aggregate instance according to the attributes of an event.
|
|
272
266
|
|
|
@@ -278,31 +272,34 @@ class CanInitAggregate(CanMutateAggregate):
|
|
|
278
272
|
# Resolve originator topic.
|
|
279
273
|
aggregate_class: Type[TAggregate] = resolve_topic(self.originator_topic)
|
|
280
274
|
|
|
281
|
-
# Construct
|
|
275
|
+
# Construct an aggregate object (a "shell" of the correct object type).
|
|
282
276
|
agg = aggregate_class.__new__(aggregate_class)
|
|
283
277
|
|
|
284
|
-
#
|
|
278
|
+
# Pick out event attributes for the aggregate base class init method.
|
|
285
279
|
base_kwargs = _filter_kwargs_for_method_params(
|
|
286
280
|
self.__dict__, type(agg).__base_init__
|
|
287
281
|
)
|
|
288
282
|
|
|
289
|
-
# Call the base class init method
|
|
283
|
+
# Call the base class init method (so we don't need to always write
|
|
284
|
+
# a call to super().__init__() in every aggregate __init__() method).
|
|
290
285
|
agg.__base_init__(**base_kwargs)
|
|
291
286
|
|
|
292
|
-
#
|
|
287
|
+
# Pick out event attributes for aggregate subclass class init method.
|
|
293
288
|
init_kwargs = _filter_kwargs_for_method_params(
|
|
294
289
|
self.__dict__, type(agg).__init__
|
|
295
290
|
)
|
|
296
291
|
|
|
297
|
-
# Provide the id, if the init method expects it.
|
|
292
|
+
# Provide the aggregate id, if the aggregate subclass init method expects it.
|
|
298
293
|
if aggregate_class in _init_mentions_id:
|
|
299
294
|
init_kwargs["id"] = self.__dict__["originator_id"]
|
|
300
295
|
|
|
301
|
-
# Call the aggregate class init method.
|
|
296
|
+
# Call the aggregate subclass class init method.
|
|
302
297
|
agg.__init__(**init_kwargs) # type: ignore
|
|
303
298
|
|
|
299
|
+
# Call the event apply method (alternative to using __init__())
|
|
304
300
|
self.apply(agg)
|
|
305
301
|
|
|
302
|
+
# Return the constructed and initialised aggregate object.
|
|
306
303
|
return agg
|
|
307
304
|
|
|
308
305
|
|
|
@@ -384,36 +381,38 @@ def _filter_kwargs_for_method_params(
|
|
|
384
381
|
|
|
385
382
|
|
|
386
383
|
@lru_cache(maxsize=None)
|
|
387
|
-
def _spec_filter_kwargs_for_method_params(method: Callable[..., Any]) ->
|
|
384
|
+
def _spec_filter_kwargs_for_method_params(method: Callable[..., Any]) -> set[str]:
|
|
388
385
|
method_signature = inspect.signature(method)
|
|
389
386
|
return set(method_signature.parameters)
|
|
390
387
|
|
|
391
388
|
|
|
392
|
-
|
|
389
|
+
if TYPE_CHECKING: # pragma: nocover
|
|
390
|
+
EventSpecType = Union[str, Type[CanMutateAggregate]]
|
|
391
|
+
|
|
393
392
|
CommandMethod = Callable[..., None]
|
|
394
393
|
DecoratedObjType = Union[CommandMethod, property]
|
|
395
394
|
TDecoratedObjType = TypeVar("TDecoratedObjType", bound=DecoratedObjType)
|
|
396
|
-
InjectEventType = bool
|
|
397
395
|
|
|
398
396
|
|
|
399
397
|
class CommandMethodDecorator:
|
|
400
398
|
def __init__(
|
|
401
399
|
self,
|
|
402
|
-
event_spec:
|
|
400
|
+
event_spec: EventSpecType | None,
|
|
403
401
|
decorated_obj: DecoratedObjType,
|
|
404
402
|
):
|
|
405
403
|
self.is_name_inferred_from_method = False
|
|
406
|
-
self.given_event_cls:
|
|
407
|
-
self.event_cls_name:
|
|
408
|
-
self.decorated_property:
|
|
404
|
+
self.given_event_cls: Type[CanMutateAggregate] | None = None
|
|
405
|
+
self.event_cls_name: str | None = None
|
|
406
|
+
self.decorated_property: property | None = None
|
|
409
407
|
self.is_property_setter = False
|
|
410
|
-
self.property_setter_arg_name:
|
|
411
|
-
self.decorated_method:
|
|
408
|
+
self.property_setter_arg_name: str | None = None
|
|
409
|
+
self.decorated_method: FunctionType | WrapperDescriptorType
|
|
412
410
|
|
|
413
411
|
# Event name has been specified.
|
|
414
412
|
if isinstance(event_spec, str):
|
|
415
413
|
if event_spec == "":
|
|
416
|
-
|
|
414
|
+
msg = "Can't use empty string as name of event class"
|
|
415
|
+
raise ValueError(msg)
|
|
417
416
|
self.event_cls_name = event_spec
|
|
418
417
|
|
|
419
418
|
# Event class has been specified.
|
|
@@ -422,7 +421,8 @@ class CommandMethodDecorator:
|
|
|
422
421
|
):
|
|
423
422
|
if event_spec in given_event_classes:
|
|
424
423
|
name = event_spec.__name__
|
|
425
|
-
|
|
424
|
+
msg = f"{name} event class used in more than one decorator"
|
|
425
|
+
raise TypeError(msg)
|
|
426
426
|
self.given_event_cls = event_spec
|
|
427
427
|
given_event_classes.add(event_spec)
|
|
428
428
|
|
|
@@ -432,9 +432,8 @@ class CommandMethodDecorator:
|
|
|
432
432
|
if decorated_obj.fset is None:
|
|
433
433
|
assert decorated_obj.fget, "Property has no getter"
|
|
434
434
|
method_name = decorated_obj.fget.__name__
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
)
|
|
435
|
+
msg = f"@event can't decorate {method_name}() property getter"
|
|
436
|
+
raise TypeError(msg)
|
|
438
437
|
|
|
439
438
|
# Remember we are decorating a property.
|
|
440
439
|
self.decorated_property = decorated_obj
|
|
@@ -447,9 +446,8 @@ class CommandMethodDecorator:
|
|
|
447
446
|
# Disallow deriving event class names from property names.
|
|
448
447
|
if not self.given_event_cls and not self.event_cls_name:
|
|
449
448
|
method_name = self.decorated_method.__name__
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
)
|
|
449
|
+
msg = f"@event on {method_name}() setter requires event name or class"
|
|
450
|
+
raise TypeError(msg)
|
|
453
451
|
|
|
454
452
|
# Remember the name of the second setter arg.
|
|
455
453
|
setter_arg_names = list(inspect.signature(self.decorated_method).parameters)
|
|
@@ -472,7 +470,8 @@ class CommandMethodDecorator:
|
|
|
472
470
|
|
|
473
471
|
# Disallow decorating other types of object.
|
|
474
472
|
else:
|
|
475
|
-
|
|
473
|
+
msg = f"{decorated_obj} is not a function or property"
|
|
474
|
+
raise TypeError(msg)
|
|
476
475
|
|
|
477
476
|
# Disallow using methods with variable params to define event class.
|
|
478
477
|
if self.event_cls_name:
|
|
@@ -496,7 +495,7 @@ class CommandMethodDecorator:
|
|
|
496
495
|
@overload
|
|
497
496
|
def __get__(
|
|
498
497
|
self, instance: None, owner: MetaAggregate[Aggregate]
|
|
499
|
-
) ->
|
|
498
|
+
) -> UnboundCommandMethodDecorator | property:
|
|
500
499
|
"""
|
|
501
500
|
Descriptor protocol for getting decorated method or property on class object.
|
|
502
501
|
"""
|
|
@@ -504,16 +503,14 @@ class CommandMethodDecorator:
|
|
|
504
503
|
@overload
|
|
505
504
|
def __get__(
|
|
506
505
|
self, instance: Aggregate, owner: MetaAggregate[Aggregate]
|
|
507
|
-
) ->
|
|
506
|
+
) -> BoundCommandMethodDecorator | Any:
|
|
508
507
|
"""
|
|
509
508
|
Descriptor protocol for getting decorated method or property on instance object.
|
|
510
509
|
"""
|
|
511
510
|
|
|
512
511
|
def __get__(
|
|
513
|
-
self, instance:
|
|
514
|
-
) ->
|
|
515
|
-
BoundCommandMethodDecorator, UnboundCommandMethodDecorator, property, Any
|
|
516
|
-
]:
|
|
512
|
+
self, instance: Aggregate | None, owner: MetaAggregate[Aggregate]
|
|
513
|
+
) -> BoundCommandMethodDecorator | UnboundCommandMethodDecorator | property | Any:
|
|
517
514
|
"""
|
|
518
515
|
Descriptor protocol for getting decorated method or property.
|
|
519
516
|
"""
|
|
@@ -522,12 +519,11 @@ class CommandMethodDecorator:
|
|
|
522
519
|
return self.decorated_property.__get__(instance, owner)
|
|
523
520
|
|
|
524
521
|
# Return a "bound" command method decorator if we have an instance.
|
|
525
|
-
|
|
522
|
+
if instance:
|
|
526
523
|
return BoundCommandMethodDecorator(self, instance)
|
|
527
524
|
|
|
528
525
|
# Return an "unbound" command method decorator if we have no instance.
|
|
529
|
-
|
|
530
|
-
return UnboundCommandMethodDecorator(self)
|
|
526
|
+
return UnboundCommandMethodDecorator(self)
|
|
531
527
|
|
|
532
528
|
def __set__(self, instance: Aggregate, value: Any) -> None:
|
|
533
529
|
"""
|
|
@@ -566,8 +562,8 @@ def event(
|
|
|
566
562
|
|
|
567
563
|
|
|
568
564
|
def event(
|
|
569
|
-
arg:
|
|
570
|
-
) ->
|
|
565
|
+
arg: EventSpecType | TDecoratedObjType | None = None,
|
|
566
|
+
) -> TDecoratedObjType | Callable[[TDecoratedObjType], TDecoratedObjType]:
|
|
571
567
|
"""
|
|
572
568
|
Event-triggering decorator for aggregate command methods and property setters.
|
|
573
569
|
|
|
@@ -627,7 +623,7 @@ def event(
|
|
|
627
623
|
Callable[[TDecoratedObjType], TDecoratedObjType], command_method_decorator
|
|
628
624
|
)
|
|
629
625
|
|
|
630
|
-
|
|
626
|
+
if (
|
|
631
627
|
arg is None
|
|
632
628
|
or isinstance(arg, str)
|
|
633
629
|
or isinstance(arg, type)
|
|
@@ -646,11 +642,11 @@ def event(
|
|
|
646
642
|
|
|
647
643
|
return create_command_method_decorator
|
|
648
644
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
645
|
+
msg = (
|
|
646
|
+
f"{arg} is not a str, function, property, or subclass of "
|
|
647
|
+
f"{CanMutateAggregate.__name__}"
|
|
648
|
+
)
|
|
649
|
+
raise TypeError(msg)
|
|
654
650
|
|
|
655
651
|
|
|
656
652
|
triggers = event
|
|
@@ -676,7 +672,8 @@ class UnboundCommandMethodDecorator:
|
|
|
676
672
|
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
|
677
673
|
# Expect first argument is an aggregate instance.
|
|
678
674
|
if len(args) < 1 or not isinstance(args[0], Aggregate):
|
|
679
|
-
|
|
675
|
+
msg = "Expected aggregate as first argument"
|
|
676
|
+
raise TypeError(msg)
|
|
680
677
|
aggregate: Aggregate = args[0]
|
|
681
678
|
assert isinstance(aggregate, Aggregate)
|
|
682
679
|
BoundCommandMethodDecorator(self.event_decorator, aggregate)(
|
|
@@ -716,7 +713,7 @@ class BoundCommandMethodDecorator:
|
|
|
716
713
|
self.trigger(*args, **kwargs)
|
|
717
714
|
|
|
718
715
|
|
|
719
|
-
given_event_classes:
|
|
716
|
+
given_event_classes: set[type] = set()
|
|
720
717
|
decorated_methods: Dict[type, CommandMethod] = {}
|
|
721
718
|
aggregate_has_many_created_event_classes: Dict[type, List[str]] = {}
|
|
722
719
|
|
|
@@ -729,24 +726,23 @@ decorated_event_classes: Dict[
|
|
|
729
726
|
def _check_no_variable_params(method: FunctionType) -> None:
|
|
730
727
|
for param in inspect.signature(method).parameters.values():
|
|
731
728
|
if param.kind is param.VAR_POSITIONAL:
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
# Todo: Support VAR_POSITIONAL?
|
|
729
|
+
msg = f"*{param.name} not supported by decorator on {method.__name__}()"
|
|
730
|
+
raise TypeError(msg)
|
|
731
|
+
# TODO: Support VAR_POSITIONAL?
|
|
736
732
|
# annotations["__star_args__"] = "typing.Any"
|
|
737
733
|
|
|
738
734
|
if param.kind is param.VAR_KEYWORD:
|
|
739
|
-
#
|
|
735
|
+
# TODO: Support VAR_KEYWORD?
|
|
740
736
|
# annotations["__star_kwargs__"] = "typing.Any"
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
)
|
|
737
|
+
msg = f"**{param.name} not supported by decorator on {method.__name__}()"
|
|
738
|
+
raise TypeError(msg)
|
|
744
739
|
|
|
745
740
|
|
|
746
741
|
def _coerce_args_to_kwargs(
|
|
747
|
-
method:
|
|
742
|
+
method: FunctionType | WrapperDescriptorType,
|
|
748
743
|
args: Iterable[Any],
|
|
749
744
|
kwargs: Dict[str, Any],
|
|
745
|
+
*,
|
|
750
746
|
expects_id: bool = False,
|
|
751
747
|
) -> Dict[str, Any]:
|
|
752
748
|
assert isinstance(method, (FunctionType, WrapperDescriptorType))
|
|
@@ -769,9 +765,10 @@ def _coerce_args_to_kwargs(
|
|
|
769
765
|
|
|
770
766
|
@lru_cache(maxsize=None)
|
|
771
767
|
def _spec_coerce_args_to_kwargs(
|
|
772
|
-
method:
|
|
768
|
+
method: FunctionType | WrapperDescriptorType,
|
|
773
769
|
len_args: int,
|
|
774
770
|
kwargs_keys: Tuple[str],
|
|
771
|
+
*,
|
|
775
772
|
expects_id: bool,
|
|
776
773
|
) -> Tuple[Tuple[Tuple[int, str], ...], Tuple[Tuple[str, Any], ...]]:
|
|
777
774
|
method_signature = inspect.signature(method)
|
|
@@ -800,9 +797,8 @@ def _spec_coerce_args_to_kwargs(
|
|
|
800
797
|
method_name = get_method_name(method)
|
|
801
798
|
for name in kwargs_keys:
|
|
802
799
|
if name not in required_keyword_only and name not in positional_names:
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
)
|
|
800
|
+
msg = f"{method_name}() got an unexpected keyword argument '{name}'"
|
|
801
|
+
raise TypeError(msg)
|
|
806
802
|
if len_args > len(positional_names):
|
|
807
803
|
msg = (
|
|
808
804
|
f"{method_name}() takes {len(positional_names) + 1} "
|
|
@@ -829,21 +825,18 @@ def _spec_coerce_args_to_kwargs(
|
|
|
829
825
|
if counter + 1 > len_args:
|
|
830
826
|
break
|
|
831
827
|
if name in kwargs_keys:
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
for name in required_keyword_only:
|
|
840
|
-
if name not in kwargs_keys:
|
|
841
|
-
missing_keyword_only_arguments.append(name)
|
|
828
|
+
msg = f"{method_name}() got multiple values for argument '{name}'"
|
|
829
|
+
raise TypeError(msg)
|
|
830
|
+
args_names.append(name)
|
|
831
|
+
counter += 1
|
|
832
|
+
missing_keyword_only_arguments = [
|
|
833
|
+
name for name in required_keyword_only if name not in kwargs_keys
|
|
834
|
+
]
|
|
842
835
|
if missing_keyword_only_arguments:
|
|
843
836
|
missing_names = [f"'{name}'" for name in missing_keyword_only_arguments]
|
|
844
837
|
msg = (
|
|
845
838
|
f"{method_name}() missing {len(missing_names)} "
|
|
846
|
-
|
|
839
|
+
"required keyword-only argument"
|
|
847
840
|
f"{'' if len(missing_names) == 1 else 's'}: "
|
|
848
841
|
)
|
|
849
842
|
_raise_missing_names_type_error(missing_names, msg)
|
|
@@ -865,10 +858,8 @@ def _raise_missing_names_type_error(missing_names: List[str], msg: str) -> None:
|
|
|
865
858
|
raise TypeError(msg)
|
|
866
859
|
|
|
867
860
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
_annotations_mention_id: Set[MetaAggregate[Aggregate]] = set()
|
|
871
|
-
_init_mentions_id: Set[MetaAggregate[Aggregate]] = set()
|
|
861
|
+
_annotations_mention_id: set[MetaAggregate[Aggregate]] = set()
|
|
862
|
+
_init_mentions_id: set[MetaAggregate[Aggregate]] = set()
|
|
872
863
|
|
|
873
864
|
|
|
874
865
|
class MetaAggregate(type, Generic[TAggregate]):
|
|
@@ -905,7 +896,7 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
905
896
|
|
|
906
897
|
_created_event_class: Type[CanInitAggregate]
|
|
907
898
|
|
|
908
|
-
def __new__(cls, *args: Any, **
|
|
899
|
+
def __new__(cls, *args: Any, **_: Any) -> MetaAggregate[Aggregate]:
|
|
909
900
|
"""
|
|
910
901
|
Configures aggregate class definition.
|
|
911
902
|
"""
|
|
@@ -957,15 +948,16 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
957
948
|
if name.lower() == name:
|
|
958
949
|
# Don't subclass lowercase named attributes that have classes.
|
|
959
950
|
continue
|
|
960
|
-
if
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
951
|
+
if (
|
|
952
|
+
isinstance(value, type)
|
|
953
|
+
and issubclass(value, AggregateEvent)
|
|
954
|
+
and not issubclass(value, base_event_cls)
|
|
955
|
+
):
|
|
956
|
+
sub_class = cls._define_event_class(name, (value, base_event_cls), None)
|
|
957
|
+
setattr(cls, name, sub_class)
|
|
966
958
|
|
|
967
959
|
# Identify or define the aggregate's "created" event class.
|
|
968
|
-
created_event_class:
|
|
960
|
+
created_event_class: Type[CanInitAggregate] | None = None
|
|
969
961
|
|
|
970
962
|
# Has the "created" event class been indicated with '_created_event_class'.
|
|
971
963
|
if "_created_event_class" in cls.__dict__:
|
|
@@ -978,15 +970,15 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
978
970
|
assert created_event_class
|
|
979
971
|
cls._created_event_class = created_event_class
|
|
980
972
|
else:
|
|
981
|
-
|
|
973
|
+
msg = (
|
|
982
974
|
f"{created_event_class} not subclass of {CanInitAggregate.__name__}"
|
|
983
975
|
)
|
|
976
|
+
raise TypeError(msg)
|
|
984
977
|
|
|
985
978
|
# Disallow using both '_created_event_class' and 'created_event_name'.
|
|
986
979
|
if created_event_class and created_event_name:
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
)
|
|
980
|
+
msg = "Can't use both '_created_event_class' and 'created_event_name'"
|
|
981
|
+
raise TypeError(msg)
|
|
990
982
|
|
|
991
983
|
# Is the init method decorated with a CommandMethodDecorator?
|
|
992
984
|
if isinstance(cls.__dict__.get("__init__"), CommandMethodDecorator):
|
|
@@ -997,13 +989,11 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
997
989
|
|
|
998
990
|
# Disallow using both 'created_event_name' and '_created_event_class'.
|
|
999
991
|
if created_event_name:
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
raise TypeError(
|
|
1005
|
-
"Can't use both '_created_event_class' and decorator on __init__"
|
|
1006
|
-
)
|
|
992
|
+
msg = "Can't use both 'created_event_name' and decorator on __init__"
|
|
993
|
+
raise TypeError(msg)
|
|
994
|
+
if created_event_class:
|
|
995
|
+
msg = "Can't use both '_created_event_class' and decorator on __init__"
|
|
996
|
+
raise TypeError(msg)
|
|
1007
997
|
|
|
1008
998
|
# Does the decorator specify a "create" event class?
|
|
1009
999
|
if init_decorator.given_event_cls:
|
|
@@ -1016,10 +1006,11 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1016
1006
|
assert created_event_class
|
|
1017
1007
|
cls._created_event_class = created_event_class
|
|
1018
1008
|
else:
|
|
1019
|
-
|
|
1009
|
+
msg = (
|
|
1020
1010
|
f"{created_event_class} not subclass of "
|
|
1021
1011
|
f"{CanInitAggregate.__name__}"
|
|
1022
1012
|
)
|
|
1013
|
+
raise TypeError(msg)
|
|
1023
1014
|
|
|
1024
1015
|
# Does the decorator specify a "created" event name?
|
|
1025
1016
|
elif init_decorator.event_cls_name:
|
|
@@ -1027,11 +1018,10 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1027
1018
|
|
|
1028
1019
|
# Disallow using decorator on __init__ without event spec.
|
|
1029
1020
|
else:
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
)
|
|
1021
|
+
msg = "Decorator on __init__ has neither event name nor class"
|
|
1022
|
+
raise TypeError(msg)
|
|
1033
1023
|
|
|
1034
|
-
#
|
|
1024
|
+
# TODO: Write a test to cover this when "Created" class is explicitly defined.
|
|
1035
1025
|
# Check if init mentions ID.
|
|
1036
1026
|
for param_name in inspect.signature(cls.__init__).parameters: # type: ignore
|
|
1037
1027
|
if param_name == "id":
|
|
@@ -1100,7 +1090,7 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1100
1090
|
|
|
1101
1091
|
# Prepare the subsequent event classes.
|
|
1102
1092
|
for attr_name, attr_value in tuple(cls.__dict__.items()):
|
|
1103
|
-
event_decorator:
|
|
1093
|
+
event_decorator: CommandMethodDecorator | None = None
|
|
1104
1094
|
|
|
1105
1095
|
if isinstance(attr_value, CommandMethodDecorator):
|
|
1106
1096
|
event_decorator = attr_value
|
|
@@ -1123,24 +1113,25 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1123
1113
|
# is a decorated method.
|
|
1124
1114
|
continue
|
|
1125
1115
|
# Otherwise, it's "x = property(getx, event(setx))".
|
|
1126
|
-
|
|
1116
|
+
elif event_decorator.is_name_inferred_from_method:
|
|
1127
1117
|
# This is the "@property.setter \ @event" form. We don't want
|
|
1128
1118
|
# event class name inferred from property (not past participle).
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1119
|
+
method_name = event_decorator.decorated_method.__name__
|
|
1120
|
+
msg = (
|
|
1121
|
+
f"@event under {method_name}() property setter requires "
|
|
1122
|
+
"event class name"
|
|
1123
|
+
)
|
|
1124
|
+
raise TypeError(msg)
|
|
1135
1125
|
|
|
1136
1126
|
if event_decorator is not None:
|
|
1137
1127
|
if event_decorator.given_event_cls:
|
|
1138
1128
|
# Check this is not a "created" event class.
|
|
1139
1129
|
if issubclass(event_decorator.given_event_cls, CanInitAggregate):
|
|
1140
|
-
|
|
1130
|
+
msg = (
|
|
1141
1131
|
f"{event_decorator.given_event_cls} "
|
|
1142
1132
|
f"is subclass of {CanInitAggregate.__name__}"
|
|
1143
1133
|
)
|
|
1134
|
+
raise TypeError(msg)
|
|
1144
1135
|
|
|
1145
1136
|
# Define event class as subclass of given class.
|
|
1146
1137
|
given_subclass = cast(
|
|
@@ -1157,10 +1148,11 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1157
1148
|
# Check event class isn't already defined.
|
|
1158
1149
|
assert event_decorator.event_cls_name
|
|
1159
1150
|
if event_decorator.event_cls_name in cls.__dict__:
|
|
1160
|
-
|
|
1151
|
+
msg = (
|
|
1161
1152
|
f"{event_decorator.event_cls_name} "
|
|
1162
1153
|
f"event already defined on {cls.__name__}"
|
|
1163
1154
|
)
|
|
1155
|
+
raise TypeError(msg)
|
|
1164
1156
|
|
|
1165
1157
|
# Define event class from signature of original method.
|
|
1166
1158
|
event_cls = cls._define_event_class(
|
|
@@ -1181,12 +1173,14 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1181
1173
|
)
|
|
1182
1174
|
|
|
1183
1175
|
# Check any create_id method defined on this class is static or class method.
|
|
1184
|
-
if "create_id" in cls.__dict__
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
)
|
|
1176
|
+
if "create_id" in cls.__dict__ and not isinstance(
|
|
1177
|
+
cls.__dict__["create_id"], (staticmethod, classmethod)
|
|
1178
|
+
):
|
|
1179
|
+
msg = (
|
|
1180
|
+
f"{cls.create_id} is not a static or class method: "
|
|
1181
|
+
f"{type(cls.create_id)}"
|
|
1182
|
+
)
|
|
1183
|
+
raise TypeError(msg)
|
|
1190
1184
|
|
|
1191
1185
|
# Get the parameters of the create_id method that will be used by this class.
|
|
1192
1186
|
cls._create_id_param_names: List[str] = []
|
|
@@ -1212,7 +1206,7 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1212
1206
|
cls,
|
|
1213
1207
|
name: str,
|
|
1214
1208
|
bases: Tuple[Type[CanMutateAggregate], ...],
|
|
1215
|
-
apply_method:
|
|
1209
|
+
apply_method: CommandMethod | None,
|
|
1216
1210
|
) -> Type[CanMutateAggregate]:
|
|
1217
1211
|
# Define annotations for the event class (specs the init method).
|
|
1218
1212
|
annotations = {}
|
|
@@ -1229,7 +1223,7 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1229
1223
|
# Don't override super class annotations, unless no default on param.
|
|
1230
1224
|
if param_name not in super_annotations or param.default == param.empty:
|
|
1231
1225
|
annotations[param_name] = param.annotation or "typing.Any"
|
|
1232
|
-
event_cls_qualname = "
|
|
1226
|
+
event_cls_qualname = f"{cls.__qualname__}.{name}"
|
|
1233
1227
|
event_cls_dict = {
|
|
1234
1228
|
"__annotations__": annotations,
|
|
1235
1229
|
"__module__": cls.__module__,
|
|
@@ -1244,15 +1238,16 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1244
1238
|
) -> TAggregate:
|
|
1245
1239
|
try:
|
|
1246
1240
|
created_event_classes = aggregate_has_many_created_event_classes[cls]
|
|
1247
|
-
|
|
1241
|
+
msg = (
|
|
1248
1242
|
"""Can't decide which of many "created" event classes to use: """
|
|
1249
1243
|
f"""'{"', '".join(created_event_classes)}'. Please use class """
|
|
1250
1244
|
"arg 'created_event_name' or @event decorator on __init__ method."
|
|
1251
1245
|
)
|
|
1246
|
+
raise TypeError(msg)
|
|
1252
1247
|
except KeyError:
|
|
1253
1248
|
pass
|
|
1254
1249
|
|
|
1255
|
-
cls_init:
|
|
1250
|
+
cls_init: FunctionType | WrapperDescriptorType = cls.__init__ # type: ignore
|
|
1256
1251
|
kwargs = _coerce_args_to_kwargs(
|
|
1257
1252
|
cls_init,
|
|
1258
1253
|
args,
|
|
@@ -1269,10 +1264,10 @@ class MetaAggregate(type, Generic[TAggregate]):
|
|
|
1269
1264
|
event_class: Type[CanInitAggregate],
|
|
1270
1265
|
**kwargs: Any,
|
|
1271
1266
|
) -> TAggregate:
|
|
1272
|
-
raise NotImplementedError
|
|
1267
|
+
raise NotImplementedError # pragma: no cover
|
|
1273
1268
|
|
|
1274
1269
|
@staticmethod
|
|
1275
|
-
def create_id(**
|
|
1270
|
+
def create_id(**_: Any) -> UUID:
|
|
1276
1271
|
"""
|
|
1277
1272
|
Returns a new aggregate ID.
|
|
1278
1273
|
"""
|
|
@@ -1291,7 +1286,7 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1291
1286
|
cls: Type[TAggregate],
|
|
1292
1287
|
event_class: Type[CanInitAggregate],
|
|
1293
1288
|
*,
|
|
1294
|
-
id:
|
|
1289
|
+
id: UUID | None = None, # noqa: A002
|
|
1295
1290
|
**kwargs: Any,
|
|
1296
1291
|
) -> TAggregate:
|
|
1297
1292
|
"""
|
|
@@ -1318,7 +1313,7 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1318
1313
|
created_event = event_class(**kwargs)
|
|
1319
1314
|
except TypeError as e:
|
|
1320
1315
|
msg = f"Unable to construct '{event_class.__name__}' event: {e}"
|
|
1321
|
-
raise TypeError(msg)
|
|
1316
|
+
raise TypeError(msg) from None
|
|
1322
1317
|
# Construct the aggregate object.
|
|
1323
1318
|
agg = cast(TAggregate, created_event.mutate(None))
|
|
1324
1319
|
|
|
@@ -1390,7 +1385,7 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1390
1385
|
class Created(Event, AggregateCreated):
|
|
1391
1386
|
pass
|
|
1392
1387
|
|
|
1393
|
-
def __eq__(self, other:
|
|
1388
|
+
def __eq__(self, other: object) -> bool:
|
|
1394
1389
|
return type(self) is type(other) and self.__dict__ == other.__dict__
|
|
1395
1390
|
|
|
1396
1391
|
def __repr__(self) -> str:
|
|
@@ -1427,7 +1422,8 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1427
1422
|
try:
|
|
1428
1423
|
new_event = event_class(**kwargs)
|
|
1429
1424
|
except TypeError as e:
|
|
1430
|
-
|
|
1425
|
+
msg = f"Can't construct event {event_class}: {e}"
|
|
1426
|
+
raise TypeError(msg) from None
|
|
1431
1427
|
|
|
1432
1428
|
# Mutate aggregate with domain event.
|
|
1433
1429
|
new_event.mutate(self)
|
|
@@ -1456,10 +1452,10 @@ class Aggregate(metaclass=MetaAggregate):
|
|
|
1456
1452
|
|
|
1457
1453
|
|
|
1458
1454
|
def aggregate(
|
|
1459
|
-
cls:
|
|
1455
|
+
cls: Any | None = None,
|
|
1460
1456
|
*,
|
|
1461
1457
|
created_event_name: str = "",
|
|
1462
|
-
) ->
|
|
1458
|
+
) -> Type[Aggregate] | Callable[[Any], Type[Aggregate]]:
|
|
1463
1459
|
"""
|
|
1464
1460
|
Converts the class that was passed in to inherit from Aggregate.
|
|
1465
1461
|
|
|
@@ -1479,13 +1475,14 @@ def aggregate(
|
|
|
1479
1475
|
|
|
1480
1476
|
def decorator(cls_: Any) -> Type[Aggregate]:
|
|
1481
1477
|
if issubclass(cls_, Aggregate):
|
|
1482
|
-
|
|
1478
|
+
msg = f"{cls_.__qualname__} is already an Aggregate"
|
|
1479
|
+
raise TypeError(msg)
|
|
1483
1480
|
bases = cls_.__bases__
|
|
1484
1481
|
if bases == (object,):
|
|
1485
1482
|
bases = (Aggregate,)
|
|
1486
1483
|
else:
|
|
1487
1484
|
bases += (Aggregate,)
|
|
1488
|
-
cls_dict =
|
|
1485
|
+
cls_dict = {}
|
|
1489
1486
|
cls_dict.update(cls_.__dict__)
|
|
1490
1487
|
cls_ = MetaAggregate(
|
|
1491
1488
|
cls_.__qualname__,
|
|
@@ -1498,8 +1495,7 @@ def aggregate(
|
|
|
1498
1495
|
|
|
1499
1496
|
if cls:
|
|
1500
1497
|
return decorator(cls)
|
|
1501
|
-
|
|
1502
|
-
return decorator
|
|
1498
|
+
return decorator
|
|
1503
1499
|
|
|
1504
1500
|
|
|
1505
1501
|
class OriginatorIDError(EventSourcingError):
|
|
@@ -1543,7 +1539,7 @@ class SnapshotProtocol(DomainEventProtocol, Protocol):
|
|
|
1543
1539
|
Snapshots have a read-only 'state'.
|
|
1544
1540
|
"""
|
|
1545
1541
|
|
|
1546
|
-
#
|
|
1542
|
+
# TODO: Improve on this 'Any'.
|
|
1547
1543
|
@classmethod
|
|
1548
1544
|
def take(cls: Any, aggregate: Any) -> Any:
|
|
1549
1545
|
"""
|
|
@@ -1574,14 +1570,13 @@ class CanSnapshotAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
|
|
|
1574
1570
|
aggregate_state.pop("_id")
|
|
1575
1571
|
aggregate_state.pop("_version")
|
|
1576
1572
|
aggregate_state.pop("_pending_events")
|
|
1577
|
-
|
|
1573
|
+
return cls( # type: ignore
|
|
1578
1574
|
originator_id=aggregate.id,
|
|
1579
1575
|
originator_version=aggregate.version,
|
|
1580
1576
|
timestamp=cls.create_timestamp(),
|
|
1581
1577
|
topic=get_topic(type(aggregate)),
|
|
1582
1578
|
state=aggregate_state,
|
|
1583
1579
|
)
|
|
1584
|
-
return snapshot
|
|
1585
1580
|
|
|
1586
1581
|
def mutate(self, _: None) -> Aggregate:
|
|
1587
1582
|
"""
|