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.

Files changed (144) hide show
  1. eventsourcing/__init__.py +1 -1
  2. eventsourcing/application.py +106 -135
  3. eventsourcing/cipher.py +15 -12
  4. eventsourcing/dispatch.py +31 -91
  5. eventsourcing/domain.py +138 -143
  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 +128 -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 +174 -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 +111 -0
  80. eventsourcing/examples/searchablecontent/test_recorder.py +69 -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 +91 -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 +361 -578
  93. eventsourcing/sqlite.py +91 -99
  94. eventsourcing/system.py +42 -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 +1159 -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 +49 -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 +1121 -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 +287 -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.0a1.dist-info}/METADATA +28 -80
  139. eventsourcing-9.3.0a1.dist-info/RECORD +144 -0
  140. {eventsourcing-9.2.22.dist-info → eventsourcing-9.3.0a1.dist-info}/WHEEL +1 -2
  141. eventsourcing-9.2.22.dist-info/AUTHORS +0 -10
  142. eventsourcing-9.2.22.dist-info/RECORD +0 -25
  143. eventsourcing-9.2.22.dist-info/top_level.txt +0 -1
  144. {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
- 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
  """
@@ -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 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)
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: Optional[Type[CanInitAggregate]] = None
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
- raise TypeError(
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
- raise TypeError(
988
- "Can't use both '_created_event_class' and 'created_event_name'"
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
- 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
- )
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
- raise TypeError(
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
- raise TypeError(
1031
- "Decorator on __init__ has neither event name nor class"
1032
- )
1021
+ msg = "Decorator on __init__ has neither event name nor class"
1022
+ raise TypeError(msg)
1033
1023
 
1034
- # Todo: Write a test to cover this when "Created" class is explicitly defined.
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: Optional[CommandMethodDecorator] = None
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
- else:
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
- 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
- )
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
- raise TypeError(
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
- raise TypeError(
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
- 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
- )
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: Optional[CommandMethod],
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 = ".".join([cls.__qualname__, name])
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
- raise TypeError(
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: Union[FunctionType, WrapperDescriptorType] = cls.__init__ # type: ignore
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() # pragma: no cover
1267
+ raise NotImplementedError # pragma: no cover
1273
1268
 
1274
1269
  @staticmethod
1275
- def create_id(**kwargs: Any) -> UUID:
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: Optional[UUID] = None,
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: Any) -> bool:
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
- raise TypeError(f"Can't construct event {event_class}: {e}")
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: Optional[Any] = None,
1455
+ cls: Any | None = None,
1460
1456
  *,
1461
1457
  created_event_name: str = "",
1462
- ) -> Union[Type[Aggregate], Callable[[Any], Type[Aggregate]]]:
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
- raise TypeError(f"{cls_.__qualname__} is already an Aggregate")
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 = 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
- else:
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
- # Todo: Improve on this 'Any'.
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
- snapshot = cls( # type: ignore
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
  """