eventsourcing 9.2.22__py3-none-any.whl → 9.3.0__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.

Files changed (144) hide show
  1. eventsourcing/__init__.py +1 -1
  2. eventsourcing/application.py +116 -135
  3. eventsourcing/cipher.py +15 -12
  4. eventsourcing/dispatch.py +31 -91
  5. eventsourcing/domain.py +220 -226
  6. eventsourcing/examples/__init__.py +0 -0
  7. eventsourcing/examples/aggregate1/__init__.py +0 -0
  8. eventsourcing/examples/aggregate1/application.py +27 -0
  9. eventsourcing/examples/aggregate1/domainmodel.py +16 -0
  10. eventsourcing/examples/aggregate1/test_application.py +37 -0
  11. eventsourcing/examples/aggregate2/__init__.py +0 -0
  12. eventsourcing/examples/aggregate2/application.py +27 -0
  13. eventsourcing/examples/aggregate2/domainmodel.py +22 -0
  14. eventsourcing/examples/aggregate2/test_application.py +37 -0
  15. eventsourcing/examples/aggregate3/__init__.py +0 -0
  16. eventsourcing/examples/aggregate3/application.py +27 -0
  17. eventsourcing/examples/aggregate3/domainmodel.py +38 -0
  18. eventsourcing/examples/aggregate3/test_application.py +37 -0
  19. eventsourcing/examples/aggregate4/__init__.py +0 -0
  20. eventsourcing/examples/aggregate4/application.py +27 -0
  21. eventsourcing/examples/aggregate4/domainmodel.py +114 -0
  22. eventsourcing/examples/aggregate4/test_application.py +38 -0
  23. eventsourcing/examples/aggregate5/__init__.py +0 -0
  24. eventsourcing/examples/aggregate5/application.py +27 -0
  25. eventsourcing/examples/aggregate5/domainmodel.py +131 -0
  26. eventsourcing/examples/aggregate5/test_application.py +38 -0
  27. eventsourcing/examples/aggregate6/__init__.py +0 -0
  28. eventsourcing/examples/aggregate6/application.py +30 -0
  29. eventsourcing/examples/aggregate6/domainmodel.py +123 -0
  30. eventsourcing/examples/aggregate6/test_application.py +38 -0
  31. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  32. eventsourcing/examples/aggregate6a/application.py +40 -0
  33. eventsourcing/examples/aggregate6a/domainmodel.py +149 -0
  34. eventsourcing/examples/aggregate6a/test_application.py +45 -0
  35. eventsourcing/examples/aggregate7/__init__.py +0 -0
  36. eventsourcing/examples/aggregate7/application.py +48 -0
  37. eventsourcing/examples/aggregate7/domainmodel.py +144 -0
  38. eventsourcing/examples/aggregate7/persistence.py +57 -0
  39. eventsourcing/examples/aggregate7/test_application.py +38 -0
  40. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +45 -0
  41. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +67 -0
  42. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  43. eventsourcing/examples/aggregate7a/application.py +56 -0
  44. eventsourcing/examples/aggregate7a/domainmodel.py +170 -0
  45. eventsourcing/examples/aggregate7a/test_application.py +46 -0
  46. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +45 -0
  47. eventsourcing/examples/aggregate8/__init__.py +0 -0
  48. eventsourcing/examples/aggregate8/application.py +47 -0
  49. eventsourcing/examples/aggregate8/domainmodel.py +65 -0
  50. eventsourcing/examples/aggregate8/persistence.py +57 -0
  51. eventsourcing/examples/aggregate8/test_application.py +37 -0
  52. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +44 -0
  53. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +38 -0
  54. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  55. eventsourcing/examples/bankaccounts/application.py +70 -0
  56. eventsourcing/examples/bankaccounts/domainmodel.py +56 -0
  57. eventsourcing/examples/bankaccounts/test.py +173 -0
  58. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  59. eventsourcing/examples/cargoshipping/application.py +126 -0
  60. eventsourcing/examples/cargoshipping/domainmodel.py +330 -0
  61. eventsourcing/examples/cargoshipping/interface.py +143 -0
  62. eventsourcing/examples/cargoshipping/test.py +231 -0
  63. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  64. eventsourcing/examples/contentmanagement/application.py +118 -0
  65. eventsourcing/examples/contentmanagement/domainmodel.py +69 -0
  66. eventsourcing/examples/contentmanagement/test.py +180 -0
  67. eventsourcing/examples/contentmanagement/utils.py +26 -0
  68. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  69. eventsourcing/examples/contentmanagementsystem/application.py +54 -0
  70. eventsourcing/examples/contentmanagementsystem/postgres.py +17 -0
  71. eventsourcing/examples/contentmanagementsystem/sqlite.py +17 -0
  72. eventsourcing/examples/contentmanagementsystem/system.py +14 -0
  73. eventsourcing/examples/contentmanagementsystem/test_system.py +180 -0
  74. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  75. eventsourcing/examples/searchablecontent/application.py +45 -0
  76. eventsourcing/examples/searchablecontent/persistence.py +23 -0
  77. eventsourcing/examples/searchablecontent/postgres.py +118 -0
  78. eventsourcing/examples/searchablecontent/sqlite.py +136 -0
  79. eventsourcing/examples/searchablecontent/test_application.py +110 -0
  80. eventsourcing/examples/searchablecontent/test_recorder.py +68 -0
  81. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  82. eventsourcing/examples/searchabletimestamps/application.py +32 -0
  83. eventsourcing/examples/searchabletimestamps/persistence.py +20 -0
  84. eventsourcing/examples/searchabletimestamps/postgres.py +110 -0
  85. eventsourcing/examples/searchabletimestamps/sqlite.py +99 -0
  86. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +94 -0
  87. eventsourcing/examples/test_invoice.py +176 -0
  88. eventsourcing/examples/test_parking_lot.py +206 -0
  89. eventsourcing/interface.py +2 -2
  90. eventsourcing/persistence.py +85 -81
  91. eventsourcing/popo.py +30 -31
  92. eventsourcing/postgres.py +379 -590
  93. eventsourcing/sqlite.py +91 -99
  94. eventsourcing/system.py +52 -57
  95. eventsourcing/tests/application.py +20 -32
  96. eventsourcing/tests/application_tests/__init__.py +0 -0
  97. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +55 -0
  98. eventsourcing/tests/application_tests/test_application_with_popo.py +22 -0
  99. eventsourcing/tests/application_tests/test_application_with_postgres.py +75 -0
  100. eventsourcing/tests/application_tests/test_application_with_sqlite.py +72 -0
  101. eventsourcing/tests/application_tests/test_cache.py +134 -0
  102. eventsourcing/tests/application_tests/test_event_sourced_log.py +162 -0
  103. eventsourcing/tests/application_tests/test_notificationlog.py +232 -0
  104. eventsourcing/tests/application_tests/test_notificationlogreader.py +126 -0
  105. eventsourcing/tests/application_tests/test_processapplication.py +110 -0
  106. eventsourcing/tests/application_tests/test_processingpolicy.py +109 -0
  107. eventsourcing/tests/application_tests/test_repository.py +504 -0
  108. eventsourcing/tests/application_tests/test_snapshotting.py +68 -0
  109. eventsourcing/tests/application_tests/test_upcasting.py +459 -0
  110. eventsourcing/tests/docs_tests/__init__.py +0 -0
  111. eventsourcing/tests/docs_tests/test_docs.py +293 -0
  112. eventsourcing/tests/domain.py +1 -1
  113. eventsourcing/tests/domain_tests/__init__.py +0 -0
  114. eventsourcing/tests/domain_tests/test_aggregate.py +1180 -0
  115. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +1604 -0
  116. eventsourcing/tests/domain_tests/test_domainevent.py +80 -0
  117. eventsourcing/tests/interface_tests/__init__.py +0 -0
  118. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +258 -0
  119. eventsourcing/tests/persistence.py +52 -50
  120. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  121. eventsourcing/tests/persistence_tests/test_aes.py +93 -0
  122. eventsourcing/tests/persistence_tests/test_connection_pool.py +722 -0
  123. eventsourcing/tests/persistence_tests/test_eventstore.py +72 -0
  124. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +21 -0
  125. eventsourcing/tests/persistence_tests/test_mapper.py +113 -0
  126. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +69 -0
  127. eventsourcing/tests/persistence_tests/test_popo.py +124 -0
  128. eventsourcing/tests/persistence_tests/test_postgres.py +1119 -0
  129. eventsourcing/tests/persistence_tests/test_sqlite.py +348 -0
  130. eventsourcing/tests/persistence_tests/test_transcoder.py +44 -0
  131. eventsourcing/tests/postgres_utils.py +7 -7
  132. eventsourcing/tests/system_tests/__init__.py +0 -0
  133. eventsourcing/tests/system_tests/test_runner.py +935 -0
  134. eventsourcing/tests/system_tests/test_system.py +284 -0
  135. eventsourcing/tests/utils_tests/__init__.py +0 -0
  136. eventsourcing/tests/utils_tests/test_utils.py +226 -0
  137. eventsourcing/utils.py +47 -50
  138. {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0.dist-info}/METADATA +29 -79
  139. eventsourcing-9.3.0.dist-info/RECORD +145 -0
  140. {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0.dist-info}/WHEEL +1 -2
  141. eventsourcing-9.2.22.dist-info/RECORD +0 -25
  142. eventsourcing-9.2.22.dist-info/top_level.txt +0 -1
  143. {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0.dist-info}/AUTHORS +0 -0
  144. {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0.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
- Optional,
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: Optional[TMutableOrImmutableAggregate]
154
- ) -> Optional[TMutableOrImmutableAggregate]:
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: Optional[TAggregate]) -> Optional[TAggregate]:
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: Optional[TAggregate]) -> Optional[TAggregate]:
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 and return aggregate object.
275
+ # Construct an aggregate object (a "shell" of the correct object type).
282
276
  agg = aggregate_class.__new__(aggregate_class)
283
277
 
284
- # Separate the base class keywords arguments.
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
- # Select values that aren't mentioned in the method signature.
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]) -> Set[str]:
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
- EventSpecType = Union[str, Type[CanMutateAggregate]]
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: Optional[EventSpecType],
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: Optional[Type[CanMutateAggregate]] = None
407
- self.event_cls_name: Optional[str] = None
408
- self.decorated_property: Optional[property] = None
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: Optional[str] = None
411
- self.decorated_method: Union[FunctionType, WrapperDescriptorType]
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
- raise ValueError("Can't use empty string as name of event class")
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
- raise TypeError(f"{name} event class used in more than one decorator")
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
- raise TypeError(
436
- f"@event can't decorate {method_name}() property getter"
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
- raise TypeError(
451
- f"@event on {method_name}() setter requires event name or class"
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
- raise TypeError(f"{decorated_obj} is not a function or property")
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
- ) -> Union[UnboundCommandMethodDecorator, property]:
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
- ) -> Union[BoundCommandMethodDecorator, Any]:
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: Optional[Aggregate], owner: MetaAggregate[Aggregate]
514
- ) -> Union[
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
- elif instance:
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
- else:
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: Optional[Union[EventSpecType, TDecoratedObjType]] = None,
570
- ) -> Union[TDecoratedObjType, Callable[[TDecoratedObjType], TDecoratedObjType]]:
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
- elif (
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
- else:
650
- raise TypeError(
651
- f"{arg} is not a str, function, property, or subclass of "
652
- f"{CanMutateAggregate.__name__}"
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
- raise TypeError("Expected aggregate as first argument")
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: Set[type] = set()
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
- raise TypeError(
733
- f"*{param.name} not supported by decorator on {method.__name__}()"
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
- # Todo: Support VAR_KEYWORD?
735
+ # TODO: Support VAR_KEYWORD?
740
736
  # annotations["__star_kwargs__"] = "typing.Any"
741
- raise TypeError(
742
- f"**{param.name} not supported by decorator on {method.__name__}()"
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: Union[FunctionType, WrapperDescriptorType],
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: Union[FunctionType, WrapperDescriptorType],
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
- raise TypeError(
804
- f"{method_name}() got an unexpected " f"keyword argument '{name}'"
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
- raise TypeError(
833
- f"{method_name}() got multiple values for argument '{name}'"
834
- )
835
- else:
836
- args_names.append(name)
837
- counter += 1
838
- missing_keyword_only_arguments = []
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
- f"required keyword-only argument"
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
- _TT = TypeVar("_TT", bound="type")
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, **kwargs: Any) -> MetaAggregate[Aggregate]:
899
+ def __new__(cls, *args: Any, **_: Any) -> MetaAggregate[Aggregate]:
909
900
  """
910
901
  Configures aggregate class definition.
911
902
  """
@@ -950,6 +941,7 @@ class MetaAggregate(type, Generic[TAggregate]):
950
941
  setattr(cls, base_event_name, base_event_cls)
951
942
 
952
943
  # Make sure all events defined on aggregate subclass the base event class.
944
+ created_event_classes: Dict[str, Type[CanInitAggregate]] = {}
953
945
  for name, value in tuple(cls.__dict__.items()):
954
946
  if name == base_event_name:
955
947
  # Don't subclass the base event class again.
@@ -957,36 +949,26 @@ class MetaAggregate(type, Generic[TAggregate]):
957
949
  if name.lower() == name:
958
950
  # Don't subclass lowercase named attributes that have classes.
959
951
  continue
960
- if isinstance(value, type) and issubclass(value, AggregateEvent):
961
- if not issubclass(value, base_event_cls):
962
- sub_class = cls._define_event_class(
963
- name, (value, base_event_cls), None
964
- )
965
- setattr(cls, name, sub_class)
966
-
967
- # Identify or define the aggregate's "created" event class.
968
- created_event_class: Optional[Type[CanInitAggregate]] = None
969
-
970
- # Has the "created" event class been indicated with '_created_event_class'.
971
- if "_created_event_class" in cls.__dict__:
972
- created_event_class = cls.__dict__["_created_event_class"]
973
- if isinstance(created_event_class, type) and issubclass(
974
- created_event_class, CanInitAggregate
952
+ if (
953
+ isinstance(value, type)
954
+ and issubclass(value, AggregateEvent)
955
+ and not issubclass(value, base_event_cls)
975
956
  ):
976
- # We just subclassed the event classes, so reassign this.
977
- created_event_class = getattr(cls, created_event_class.__name__)
978
- assert created_event_class
979
- cls._created_event_class = created_event_class
980
- else:
981
- raise TypeError(
982
- f"{created_event_class} not subclass of {CanInitAggregate.__name__}"
983
- )
957
+ sub_class = cls._define_event_class(name, (value, base_event_cls), None)
958
+ setattr(cls, name, sub_class)
959
+ for name, value in tuple(cls.__dict__.items()):
960
+ if isinstance(value, type) and issubclass(value, CanInitAggregate):
961
+ created_event_classes[name] = value
984
962
 
985
963
  # Disallow using both '_created_event_class' and 'created_event_name'.
964
+ created_event_class: Type[CanInitAggregate] | None = cls.__dict__.get(
965
+ "_created_event_class"
966
+ )
986
967
  if created_event_class and created_event_name:
987
- raise TypeError(
988
- "Can't use both '_created_event_class' and 'created_event_name'"
989
- )
968
+ msg = "Can't use both '_created_event_class' and 'created_event_name'"
969
+ raise TypeError(msg)
970
+
971
+ # Identify or define the aggregate's "created" event class.
990
972
 
991
973
  # Is the init method decorated with a CommandMethodDecorator?
992
974
  if isinstance(cls.__dict__.get("__init__"), CommandMethodDecorator):
@@ -995,112 +977,119 @@ class MetaAggregate(type, Generic[TAggregate]):
995
977
  # Set the original method on the class (un-decorate __init__).
996
978
  cls.__init__ = init_decorator.decorated_method # type: ignore
997
979
 
998
- # Disallow using both 'created_event_name' and '_created_event_class'.
980
+ # Disallow using both 'created_event_name' and decorator on __init__.
999
981
  if created_event_name:
1000
- raise TypeError(
1001
- "Can't use both 'created_event_name' and decorator on __init__"
1002
- )
1003
- elif created_event_class:
1004
- raise TypeError(
1005
- "Can't use both '_created_event_class' and decorator on __init__"
1006
- )
1007
-
1008
- # Does the decorator specify a "create" event class?
982
+ msg = "Can't use both 'created_event_name' and decorator on __init__"
983
+ raise TypeError(msg)
984
+ # Disallow using both '_created_event_class' and decorator on __init__.
985
+ if created_event_class:
986
+ msg = "Can't use both '_created_event_class' and decorator on __init__"
987
+ raise TypeError(msg)
988
+
989
+ # Does the decorator specify a "created" event class?
1009
990
  if init_decorator.given_event_cls:
1010
- created_event_class = getattr(
1011
- cls, init_decorator.given_event_cls.__name__
991
+ created_event_class = cast(
992
+ Type[CanInitAggregate], init_decorator.given_event_cls
1012
993
  )
1013
- if isinstance(created_event_class, type) and issubclass(
1014
- created_event_class, CanInitAggregate
1015
- ):
1016
- assert created_event_class
1017
- cls._created_event_class = created_event_class
1018
- else:
1019
- raise TypeError(
1020
- f"{created_event_class} not subclass of "
1021
- f"{CanInitAggregate.__name__}"
1022
- )
1023
-
1024
994
  # Does the decorator specify a "created" event name?
1025
995
  elif init_decorator.event_cls_name:
1026
996
  created_event_name = init_decorator.event_cls_name
1027
997
 
1028
998
  # Disallow using decorator on __init__ without event spec.
1029
999
  else:
1030
- raise TypeError(
1031
- "Decorator on __init__ has neither event name nor class"
1032
- )
1000
+ msg = "Decorator on __init__ has neither event name nor class"
1001
+ raise TypeError(msg)
1033
1002
 
1034
- # Todo: Write a test to cover this when "Created" class is explicitly defined.
1003
+ # TODO: Write a test to cover this when "Created" class is explicitly defined.
1035
1004
  # Check if init mentions ID.
1036
1005
  for param_name in inspect.signature(cls.__init__).parameters: # type: ignore
1037
1006
  if param_name == "id":
1038
1007
  _init_mentions_id.add(cls)
1039
1008
  break
1040
1009
 
1041
- # If no "created" event class has been specified, find or create one.
1042
- if created_event_class is None:
1043
- # Discover all the "created" event classes already defined.
1044
- created_event_classes: Dict[str, Type[AggregateCreated]] = {}
1045
- for name, value in tuple(cls.__dict__.items()):
1046
- if isinstance(value, type) and issubclass(value, AggregateCreated):
1047
- created_event_classes[name] = value
1048
-
1049
- # Is a "created" event class already defined that matches the name?
1050
- if created_event_name in created_event_classes:
1051
- cls._created_event_class = created_event_classes[created_event_name]
1052
-
1053
- # If there is only one class defined, and we have no name, use it.
1054
- elif len(created_event_classes) == 1 and not created_event_name:
1055
- cls._created_event_class = next(iter(created_event_classes.values()))
1056
-
1057
- # If there are no "created" event classes already defined, or a name is
1058
- # specified that hasn't matched, then define a "created" event class.
1059
- elif len(created_event_classes) == 0 or created_event_name:
1060
- # If no "created" event name has been specified, use default name.
1061
- if not created_event_name:
1062
- # This is safe because len(created_event_classes) == 0.
1063
- created_event_name = "Created"
1064
-
1065
- # Disallow init method from having variable params if
1066
- # we are using it to define a "created" event class.
1010
+ if created_event_class:
1011
+ # Check specified "created" event class can init aggregate.
1012
+ if not issubclass(created_event_class, CanInitAggregate):
1013
+ msg = (
1014
+ f"{created_event_class} not subclass of {CanInitAggregate.__name__}"
1015
+ )
1016
+ raise TypeError(msg)
1017
+
1018
+ for sub_class in created_event_classes.values():
1019
+ if issubclass(sub_class, created_event_class):
1020
+ # We just subclassed the created event class, so reassign it.
1021
+ created_event_class = sub_class
1022
+
1023
+ # Is a "created" event class already defined that matches the name?
1024
+ elif created_event_name and created_event_name in created_event_classes:
1025
+ created_event_class = created_event_classes[created_event_name]
1026
+
1027
+ # If there is only one class defined, then use it.
1028
+ elif len(created_event_classes) == 1 and not created_event_name:
1029
+ created_event_class = next(iter(created_event_classes.values()))
1030
+
1031
+ # If there are no "created" event classes already defined, or a name is
1032
+ # specified that hasn't matched, then define a "created" event class.
1033
+ elif len(created_event_classes) == 0 or created_event_name:
1034
+
1035
+ # Decide the base classes for the new "created" event class.
1036
+ if created_event_name and len(created_event_classes) == 1:
1037
+ base_created_event_cls = next(iter(created_event_classes.values()))
1038
+ else:
1039
+ for base_cls in cls.__mro__:
1040
+ if base_cls is cls:
1041
+ continue
1042
+ base_created_event_cls = base_cls.__dict__.get(
1043
+ "_created_event_class",
1044
+ base_cls.__dict__.get("Created"),
1045
+ )
1046
+ if base_created_event_cls:
1047
+ break
1048
+ else: # pragma: no cover
1049
+ msg = "Can't decide base class for new 'created' event class"
1050
+ raise TypeError(msg)
1051
+
1052
+ if not created_event_name:
1053
+ created_event_name = base_created_event_cls.__name__
1054
+
1055
+ # Disallow init method from having variable params if
1056
+ # we are using it to define a "created" event class.
1057
+ try:
1058
+ init_method = cls.__dict__["__init__"]
1059
+ except KeyError:
1060
+ init_method = None
1061
+ else:
1067
1062
  try:
1068
- init_method = cls.__dict__["__init__"]
1069
- except KeyError:
1070
- init_method = None
1071
- else:
1072
- try:
1073
- _check_no_variable_params(init_method)
1074
- except TypeError:
1075
- raise
1076
-
1077
- # Define a "created" event class for this aggregate.
1078
- if issubclass(cls.Created, base_event_cls):
1079
- # Don't subclass from base event class twice.
1080
- bases: Tuple[Type[CanMutateAggregate], ...] = (cls.Created,)
1081
- else:
1082
- bases = (cls.Created, base_event_cls)
1083
- event_cls = cls._define_event_class(
1063
+ _check_no_variable_params(init_method)
1064
+ except TypeError:
1065
+ raise
1066
+
1067
+ # Define a "created" event class for this aggregate.
1068
+ if issubclass(base_created_event_cls, base_event_cls):
1069
+ # Don't subclass from base event class twice.
1070
+ bases: Tuple[Type[CanMutateAggregate], ...] = (base_created_event_cls,)
1071
+ else:
1072
+ bases = (base_created_event_cls, base_event_cls)
1073
+ created_event_class = cast(
1074
+ Type[CanInitAggregate],
1075
+ cls._define_event_class(
1084
1076
  created_event_name,
1085
1077
  bases,
1086
1078
  init_method,
1087
- )
1088
-
1089
- # Set the event class as an attribute of the aggregate class.
1090
- setattr(cls, created_event_name, event_cls)
1091
-
1092
- # Remember which is the "created" event class.
1093
- cls._created_event_class = cast(Type[AggregateCreated], event_cls)
1079
+ ),
1080
+ )
1081
+ # Set the event class as an attribute of the aggregate class.
1082
+ setattr(cls, created_event_name, created_event_class)
1094
1083
 
1084
+ if created_event_class:
1085
+ cls._created_event_class = created_event_class
1086
+ else:
1095
1087
  # Prepare to disallow ambiguity of choice between created event classes.
1096
- else:
1097
- aggregate_has_many_created_event_classes[cls] = list(
1098
- created_event_classes
1099
- )
1088
+ aggregate_has_many_created_event_classes[cls] = list(created_event_classes)
1100
1089
 
1101
1090
  # Prepare the subsequent event classes.
1102
1091
  for attr_name, attr_value in tuple(cls.__dict__.items()):
1103
- event_decorator: Optional[CommandMethodDecorator] = None
1092
+ event_decorator: CommandMethodDecorator | None = None
1104
1093
 
1105
1094
  if isinstance(attr_value, CommandMethodDecorator):
1106
1095
  event_decorator = attr_value
@@ -1123,24 +1112,25 @@ class MetaAggregate(type, Generic[TAggregate]):
1123
1112
  # is a decorated method.
1124
1113
  continue
1125
1114
  # Otherwise, it's "x = property(getx, event(setx))".
1126
- else:
1115
+ elif event_decorator.is_name_inferred_from_method:
1127
1116
  # This is the "@property.setter \ @event" form. We don't want
1128
1117
  # event class name inferred from property (not past participle).
1129
- if event_decorator.is_name_inferred_from_method:
1130
- method_name = event_decorator.decorated_method.__name__
1131
- raise TypeError(
1132
- f"@event under {method_name}() property setter requires "
1133
- f"event class name"
1134
- )
1118
+ method_name = event_decorator.decorated_method.__name__
1119
+ msg = (
1120
+ f"@event under {method_name}() property setter requires "
1121
+ "event class name"
1122
+ )
1123
+ raise TypeError(msg)
1135
1124
 
1136
1125
  if event_decorator is not None:
1137
1126
  if event_decorator.given_event_cls:
1138
1127
  # Check this is not a "created" event class.
1139
1128
  if issubclass(event_decorator.given_event_cls, CanInitAggregate):
1140
- raise TypeError(
1129
+ msg = (
1141
1130
  f"{event_decorator.given_event_cls} "
1142
1131
  f"is subclass of {CanInitAggregate.__name__}"
1143
1132
  )
1133
+ raise TypeError(msg)
1144
1134
 
1145
1135
  # Define event class as subclass of given class.
1146
1136
  given_subclass = cast(
@@ -1157,10 +1147,11 @@ class MetaAggregate(type, Generic[TAggregate]):
1157
1147
  # Check event class isn't already defined.
1158
1148
  assert event_decorator.event_cls_name
1159
1149
  if event_decorator.event_cls_name in cls.__dict__:
1160
- raise TypeError(
1150
+ msg = (
1161
1151
  f"{event_decorator.event_cls_name} "
1162
1152
  f"event already defined on {cls.__name__}"
1163
1153
  )
1154
+ raise TypeError(msg)
1164
1155
 
1165
1156
  # Define event class from signature of original method.
1166
1157
  event_cls = cls._define_event_class(
@@ -1181,12 +1172,14 @@ class MetaAggregate(type, Generic[TAggregate]):
1181
1172
  )
1182
1173
 
1183
1174
  # Check any create_id method defined on this class is static or class method.
1184
- if "create_id" in cls.__dict__:
1185
- if not isinstance(cls.__dict__["create_id"], (staticmethod, classmethod)):
1186
- raise TypeError(
1187
- f"{cls.create_id} is not a static or class method: "
1188
- f"{type(cls.create_id)}"
1189
- )
1175
+ if "create_id" in cls.__dict__ and not isinstance(
1176
+ cls.__dict__["create_id"], (staticmethod, classmethod)
1177
+ ):
1178
+ msg = (
1179
+ f"{cls.create_id} is not a static or class method: "
1180
+ f"{type(cls.create_id)}"
1181
+ )
1182
+ raise TypeError(msg)
1190
1183
 
1191
1184
  # Get the parameters of the create_id method that will be used by this class.
1192
1185
  cls._create_id_param_names: List[str] = []
@@ -1212,7 +1205,7 @@ class MetaAggregate(type, Generic[TAggregate]):
1212
1205
  cls,
1213
1206
  name: str,
1214
1207
  bases: Tuple[Type[CanMutateAggregate], ...],
1215
- apply_method: Optional[CommandMethod],
1208
+ apply_method: CommandMethod | None,
1216
1209
  ) -> Type[CanMutateAggregate]:
1217
1210
  # Define annotations for the event class (specs the init method).
1218
1211
  annotations = {}
@@ -1229,7 +1222,7 @@ class MetaAggregate(type, Generic[TAggregate]):
1229
1222
  # Don't override super class annotations, unless no default on param.
1230
1223
  if param_name not in super_annotations or param.default == param.empty:
1231
1224
  annotations[param_name] = param.annotation or "typing.Any"
1232
- event_cls_qualname = ".".join([cls.__qualname__, name])
1225
+ event_cls_qualname = f"{cls.__qualname__}.{name}"
1233
1226
  event_cls_dict = {
1234
1227
  "__annotations__": annotations,
1235
1228
  "__module__": cls.__module__,
@@ -1244,15 +1237,16 @@ class MetaAggregate(type, Generic[TAggregate]):
1244
1237
  ) -> TAggregate:
1245
1238
  try:
1246
1239
  created_event_classes = aggregate_has_many_created_event_classes[cls]
1247
- raise TypeError(
1240
+ msg = (
1248
1241
  """Can't decide which of many "created" event classes to use: """
1249
1242
  f"""'{"', '".join(created_event_classes)}'. Please use class """
1250
1243
  "arg 'created_event_name' or @event decorator on __init__ method."
1251
1244
  )
1245
+ raise TypeError(msg)
1252
1246
  except KeyError:
1253
1247
  pass
1254
1248
 
1255
- cls_init: Union[FunctionType, WrapperDescriptorType] = cls.__init__ # type: ignore
1249
+ cls_init: FunctionType | WrapperDescriptorType = cls.__init__ # type: ignore
1256
1250
  kwargs = _coerce_args_to_kwargs(
1257
1251
  cls_init,
1258
1252
  args,
@@ -1269,10 +1263,10 @@ class MetaAggregate(type, Generic[TAggregate]):
1269
1263
  event_class: Type[CanInitAggregate],
1270
1264
  **kwargs: Any,
1271
1265
  ) -> TAggregate:
1272
- raise NotImplementedError() # pragma: no cover
1266
+ raise NotImplementedError # pragma: no cover
1273
1267
 
1274
1268
  @staticmethod
1275
- def create_id(**kwargs: Any) -> UUID:
1269
+ def create_id(**_: Any) -> UUID:
1276
1270
  """
1277
1271
  Returns a new aggregate ID.
1278
1272
  """
@@ -1291,7 +1285,7 @@ class Aggregate(metaclass=MetaAggregate):
1291
1285
  cls: Type[TAggregate],
1292
1286
  event_class: Type[CanInitAggregate],
1293
1287
  *,
1294
- id: Optional[UUID] = None,
1288
+ id: UUID | None = None, # noqa: A002
1295
1289
  **kwargs: Any,
1296
1290
  ) -> TAggregate:
1297
1291
  """
@@ -1318,7 +1312,7 @@ class Aggregate(metaclass=MetaAggregate):
1318
1312
  created_event = event_class(**kwargs)
1319
1313
  except TypeError as e:
1320
1314
  msg = f"Unable to construct '{event_class.__name__}' event: {e}"
1321
- raise TypeError(msg)
1315
+ raise TypeError(msg) from None
1322
1316
  # Construct the aggregate object.
1323
1317
  agg = cast(TAggregate, created_event.mutate(None))
1324
1318
 
@@ -1390,7 +1384,7 @@ class Aggregate(metaclass=MetaAggregate):
1390
1384
  class Created(Event, AggregateCreated):
1391
1385
  pass
1392
1386
 
1393
- def __eq__(self, other: Any) -> bool:
1387
+ def __eq__(self, other: object) -> bool:
1394
1388
  return type(self) is type(other) and self.__dict__ == other.__dict__
1395
1389
 
1396
1390
  def __repr__(self) -> str:
@@ -1427,7 +1421,8 @@ class Aggregate(metaclass=MetaAggregate):
1427
1421
  try:
1428
1422
  new_event = event_class(**kwargs)
1429
1423
  except TypeError as e:
1430
- raise TypeError(f"Can't construct event {event_class}: {e}")
1424
+ msg = f"Can't construct event {event_class}: {e}"
1425
+ raise TypeError(msg) from None
1431
1426
 
1432
1427
  # Mutate aggregate with domain event.
1433
1428
  new_event.mutate(self)
@@ -1456,10 +1451,10 @@ class Aggregate(metaclass=MetaAggregate):
1456
1451
 
1457
1452
 
1458
1453
  def aggregate(
1459
- cls: Optional[Any] = None,
1454
+ cls: Any | None = None,
1460
1455
  *,
1461
1456
  created_event_name: str = "",
1462
- ) -> Union[Type[Aggregate], Callable[[Any], Type[Aggregate]]]:
1457
+ ) -> Type[Aggregate] | Callable[[Any], Type[Aggregate]]:
1463
1458
  """
1464
1459
  Converts the class that was passed in to inherit from Aggregate.
1465
1460
 
@@ -1479,13 +1474,14 @@ def aggregate(
1479
1474
 
1480
1475
  def decorator(cls_: Any) -> Type[Aggregate]:
1481
1476
  if issubclass(cls_, Aggregate):
1482
- raise TypeError(f"{cls_.__qualname__} is already an Aggregate")
1477
+ msg = f"{cls_.__qualname__} is already an Aggregate"
1478
+ raise TypeError(msg)
1483
1479
  bases = cls_.__bases__
1484
1480
  if bases == (object,):
1485
1481
  bases = (Aggregate,)
1486
1482
  else:
1487
1483
  bases += (Aggregate,)
1488
- cls_dict = dict()
1484
+ cls_dict = {}
1489
1485
  cls_dict.update(cls_.__dict__)
1490
1486
  cls_ = MetaAggregate(
1491
1487
  cls_.__qualname__,
@@ -1498,8 +1494,7 @@ def aggregate(
1498
1494
 
1499
1495
  if cls:
1500
1496
  return decorator(cls)
1501
- else:
1502
- return decorator
1497
+ return decorator
1503
1498
 
1504
1499
 
1505
1500
  class OriginatorIDError(EventSourcingError):
@@ -1543,7 +1538,7 @@ class SnapshotProtocol(DomainEventProtocol, Protocol):
1543
1538
  Snapshots have a read-only 'state'.
1544
1539
  """
1545
1540
 
1546
- # Todo: Improve on this 'Any'.
1541
+ # TODO: Improve on this 'Any'.
1547
1542
  @classmethod
1548
1543
  def take(cls: Any, aggregate: Any) -> Any:
1549
1544
  """
@@ -1574,14 +1569,13 @@ class CanSnapshotAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
1574
1569
  aggregate_state.pop("_id")
1575
1570
  aggregate_state.pop("_version")
1576
1571
  aggregate_state.pop("_pending_events")
1577
- snapshot = cls( # type: ignore
1572
+ return cls( # type: ignore
1578
1573
  originator_id=aggregate.id,
1579
1574
  originator_version=aggregate.version,
1580
1575
  timestamp=cls.create_timestamp(),
1581
1576
  topic=get_topic(type(aggregate)),
1582
1577
  state=aggregate_state,
1583
1578
  )
1584
- return snapshot
1585
1579
 
1586
1580
  def mutate(self, _: None) -> Aggregate:
1587
1581
  """