eventsourcing 9.4.0b2__py3-none-any.whl → 9.4.0b3__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/domain.py CHANGED
@@ -24,13 +24,13 @@ from typing import (
24
24
  from uuid import UUID, uuid4
25
25
  from warnings import warn
26
26
 
27
- from typing_extensions import Self
28
-
29
27
  from eventsourcing.utils import get_method_name, get_topic, resolve_topic
30
28
 
31
29
  if TYPE_CHECKING:
32
30
  from collections.abc import Iterable, Sequence
33
31
 
32
+ from typing_extensions import Self
33
+
34
34
 
35
35
  TZINFO: tzinfo = resolve_topic(os.getenv("TZINFO_TOPIC", "datetime:timezone.utc"))
36
36
  """
@@ -45,9 +45,7 @@ domain and convert to local timezones when presenting values in user interfaces.
45
45
 
46
46
 
47
47
  class EventsourcingType(type):
48
- """
49
- Base type for event sourcing domain model types (aggregates and events).
50
- """
48
+ """Base type for event sourcing domain model types (aggregates and events)."""
51
49
 
52
50
 
53
51
  _T = TypeVar("_T")
@@ -78,8 +76,7 @@ patch_dataclasses_process_class()
78
76
 
79
77
  @runtime_checkable
80
78
  class DomainEventProtocol(Protocol):
81
- """
82
- Protocol for domain event objects.
79
+ """Protocol for domain event objects.
83
80
 
84
81
  A protocol is defined to allow the event sourcing mechanisms
85
82
  to work with different kinds of domain event classes. Whilst
@@ -93,16 +90,12 @@ class DomainEventProtocol(Protocol):
93
90
 
94
91
  @property
95
92
  def originator_id(self) -> UUID:
96
- """
97
- UUID identifying an aggregate to which the event belongs.
98
- """
93
+ """UUID identifying an aggregate to which the event belongs."""
99
94
  raise NotImplementedError # pragma: no cover
100
95
 
101
96
  @property
102
97
  def originator_version(self) -> int:
103
- """
104
- Integer identifying the version of the aggregate when the event occurred.
105
- """
98
+ """Integer identifying the version of the aggregate when the event occurred."""
106
99
  raise NotImplementedError # pragma: no cover
107
100
 
108
101
 
@@ -111,8 +104,7 @@ SDomainEvent = TypeVar("SDomainEvent", bound=DomainEventProtocol)
111
104
 
112
105
 
113
106
  class MutableAggregateProtocol(Protocol):
114
- """
115
- Protocol for mutable aggregate objects.
107
+ """Protocol for mutable aggregate objects.
116
108
 
117
109
  A protocol is defined to allow the event sourcing mechanisms
118
110
  to work with different kinds of aggregate classes. Whilst
@@ -123,29 +115,22 @@ class MutableAggregateProtocol(Protocol):
123
115
 
124
116
  @property
125
117
  def id(self) -> UUID:
126
- """
127
- Mutable aggregates have a read-only ID that is a UUID.
128
- """
118
+ """Mutable aggregates have a read-only ID that is a UUID."""
129
119
  raise NotImplementedError # pragma: no cover
130
120
 
131
121
  @property
132
122
  def version(self) -> int:
133
- """
134
- Mutable aggregates have a read-write version that is an int.
135
- """
123
+ """Mutable aggregates have a read-write version that is an int."""
136
124
  raise NotImplementedError # pragma: no cover
137
125
 
138
126
  @version.setter
139
127
  def version(self, value: int) -> None:
140
- """
141
- Mutable aggregates have a read-write version that is an int.
142
- """
128
+ """Mutable aggregates have a read-write version that is an int."""
143
129
  raise NotImplementedError # pragma: no cover
144
130
 
145
131
 
146
132
  class ImmutableAggregateProtocol(Protocol):
147
- """
148
- Protocol for immutable aggregate objects.
133
+ """Protocol for immutable aggregate objects.
149
134
 
150
135
  A protocol is defined to allow the event sourcing mechanisms
151
136
  to work with different kinds of aggregate classes. Whilst
@@ -156,16 +141,12 @@ class ImmutableAggregateProtocol(Protocol):
156
141
 
157
142
  @property
158
143
  def id(self) -> UUID:
159
- """
160
- Immutable aggregates have a read-only ID that is a UUID.
161
- """
144
+ """Immutable aggregates have a read-only ID that is a UUID."""
162
145
  raise NotImplementedError # pragma: no cover
163
146
 
164
147
  @property
165
148
  def version(self) -> int:
166
- """
167
- Immutable aggregates have a read-only version that is an int.
168
- """
149
+ """Immutable aggregates have a read-only version that is an int."""
169
150
  raise NotImplementedError # pragma: no cover
170
151
 
171
152
 
@@ -183,22 +164,16 @@ TMutableOrImmutableAggregate = TypeVar(
183
164
 
184
165
  @runtime_checkable
185
166
  class CollectEventsProtocol(Protocol):
186
- """
187
- Protocol for aggregates that support collecting pending events.
188
- """
167
+ """Protocol for aggregates that support collecting pending events."""
189
168
 
190
169
  def collect_events(self) -> Sequence[DomainEventProtocol]:
191
- """
192
- Returns a sequence of events.
193
- """
170
+ """Returns a sequence of events."""
194
171
  raise NotImplementedError # pragma: no cover
195
172
 
196
173
 
197
174
  @runtime_checkable
198
175
  class CanMutateProtocol(DomainEventProtocol, Protocol[TMutableOrImmutableAggregate]):
199
- """
200
- Protocol for events that have a mutate method.
201
- """
176
+ """Protocol for events that have a mutate method."""
202
177
 
203
178
  def mutate(
204
179
  self, aggregate: TMutableOrImmutableAggregate | None
@@ -212,7 +187,8 @@ class CanMutateProtocol(DomainEventProtocol, Protocol[TMutableOrImmutableAggrega
212
187
 
213
188
  def datetime_now_with_tzinfo() -> datetime:
214
189
  """
215
- Constructs a timezone-aware :class:`datetime` object for the current date and time.
190
+ Constructs a timezone-aware :class:`datetime`
191
+ object for the current date and time.
216
192
 
217
193
  Uses :py:obj:`TZINFO` as the timezone.
218
194
  """
@@ -220,9 +196,7 @@ def datetime_now_with_tzinfo() -> datetime:
220
196
 
221
197
 
222
198
  def create_utc_datetime_now() -> datetime:
223
- """
224
- Deprected in favour of :func:`~eventsourcing.domain.datetime_now_with_tzinfo`.
225
- """
199
+ """Deprected in favour of :func:`~eventsourcing.domain.datetime_now_with_tzinfo`."""
226
200
  msg = (
227
201
  "'create_utc_datetime_now()' is deprecated, "
228
202
  "use 'datetime_now_with_tzinfo()' instead"
@@ -232,14 +206,11 @@ def create_utc_datetime_now() -> datetime:
232
206
 
233
207
 
234
208
  class CanCreateTimestamp:
235
- """
236
- Provides a create_timestamp() method to subclasses.
237
- """
209
+ """Provides a create_timestamp() method to subclasses."""
238
210
 
239
211
  @staticmethod
240
212
  def create_timestamp() -> datetime:
241
- """
242
- Constructs a timezone-aware :class:`datetime` object
213
+ """Constructs a timezone-aware :class:`datetime` object
243
214
  representing when an event occurred.
244
215
  """
245
216
  return datetime_now_with_tzinfo()
@@ -249,9 +220,7 @@ TAggregate = TypeVar("TAggregate", bound="BaseAggregate")
249
220
 
250
221
 
251
222
  class HasOriginatorIDVersion:
252
- """
253
- Declares ``originator_id`` and ``originator_version`` attributes.
254
- """
223
+ """Declares ``originator_id`` and ``originator_version`` attributes."""
255
224
 
256
225
  originator_id: UUID
257
226
  """UUID identifying an aggregate to which the event belongs."""
@@ -260,19 +229,18 @@ class HasOriginatorIDVersion:
260
229
 
261
230
 
262
231
  class CanMutateAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
263
- """
264
- Implements a :func:`~eventsourcing.domain.CanMutateAggregate.mutate`
232
+ """Implements a :func:`~eventsourcing.domain.CanMutateAggregate.mutate`
265
233
  method that evolves the state of an aggregate.
266
234
  """
267
235
 
268
- # Todo: Move this to a HasTimestamp? Why is it here??
236
+ # TODO: Move this to a HasTimestamp? Why is it here??
269
237
  timestamp: datetime
270
238
  """Timezone-aware :class:`datetime` object representing when an event occurred."""
271
239
 
272
240
  def mutate(self, aggregate: TAggregate | None) -> TAggregate | None:
273
- """
274
- Validates and adjusted the attributes of the given ``aggregate`` argument. The
275
- argument is typed as ``Optional`` but the value is expected to be not ``None``.
241
+ """Validates and adjustes the attributes of the given ``aggregate`
242
+ argument. The argument typed as ``Optional`` but the value is
243
+ expected to be not ``None``.
276
244
 
277
245
  Validates the ``aggregate`` argument by checking the event's
278
246
  :py:attr:`~eventsourcing.domain.HasOriginatorIDVersion.originator_id` equals the
@@ -313,8 +281,7 @@ class CanMutateAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
313
281
  return aggregate
314
282
 
315
283
  def apply(self, aggregate: Any) -> None:
316
- """
317
- Applies the domain event to its aggregate.
284
+ """Applies the domain event to its aggregate.
318
285
 
319
286
  This method does nothing but exist to be
320
287
  overridden as a convenient way for users
@@ -324,8 +291,7 @@ class CanMutateAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
324
291
 
325
292
 
326
293
  class CanInitAggregate(CanMutateAggregate):
327
- """
328
- Implements a :func:`~eventsourcing.domain.CanMutateAggregate.mutate`
294
+ """Implements a :func:`~eventsourcing.domain.CanMutateAggregate.mutate`
329
295
  method that constructs the initial state of an aggregate.
330
296
  """
331
297
 
@@ -333,8 +299,7 @@ class CanInitAggregate(CanMutateAggregate):
333
299
  """String describing the path to an aggregate class."""
334
300
 
335
301
  def mutate(self, aggregate: TAggregate | None) -> TAggregate | None:
336
- """
337
- Constructs an aggregate instance according to the attributes of an event.
302
+ """Constructs an aggregate instance according to the attributes of an event.
338
303
 
339
304
  The ``aggregate`` argument is typed as an optional argument, but the
340
305
  value is expected to be ``None``.
@@ -361,12 +326,12 @@ class CanInitAggregate(CanMutateAggregate):
361
326
  self.__dict__, type(agg).__init__
362
327
  )
363
328
 
364
- # Provide the aggregate id, if the aggregate subclass init method expects it.
329
+ # Provide the aggregate id, if the __init__ method expects it.
365
330
  if aggregate_class in _init_mentions_id:
366
331
  init_kwargs["id"] = self.__dict__["originator_id"]
367
332
 
368
333
  # Call the aggregate subclass class init method.
369
- agg.__init__(**init_kwargs) # type: ignore
334
+ agg.__init__(**init_kwargs) # type: ignore[misc]
370
335
 
371
336
  # Call the event apply method (alternative to using __init__())
372
337
  self.apply(agg)
@@ -376,26 +341,22 @@ class CanInitAggregate(CanMutateAggregate):
376
341
 
377
342
 
378
343
  class MetaDomainEvent(EventsourcingType):
379
- """
380
- Metaclass which ensures all domain event classes are frozen dataclasses.
381
- """
344
+ """Metaclass which ensures all domain event classes are frozen dataclasses."""
382
345
 
383
346
  def __new__(
384
347
  cls, name: str, bases: tuple[type[TDomainEvent], ...], cls_dict: dict[str, Any]
385
348
  ) -> type[TDomainEvent]:
386
349
  event_cls = cast(
387
- type[TDomainEvent], super().__new__(cls, name, bases, cls_dict)
350
+ "type[TDomainEvent]", super().__new__(cls, name, bases, cls_dict)
388
351
  )
389
352
  event_cls = dataclasses.dataclass(frozen=True)(event_cls)
390
- event_cls.__signature__ = inspect.signature(event_cls.__init__) # type: ignore
353
+ event_cls.__signature__ = inspect.signature(event_cls.__init__) # type: ignore[attr-defined]
391
354
  return event_cls
392
355
 
393
356
 
394
357
  @dataclass(frozen=True)
395
358
  class DomainEvent(CanCreateTimestamp, metaclass=MetaDomainEvent):
396
- """
397
- Frozen data class representing domain model events.
398
- """
359
+ """Frozen data class representing domain model events."""
399
360
 
400
361
  originator_id: UUID
401
362
  """UUID identifying an aggregate to which the event belongs."""
@@ -406,8 +367,7 @@ class DomainEvent(CanCreateTimestamp, metaclass=MetaDomainEvent):
406
367
 
407
368
 
408
369
  class AggregateEvent(CanMutateAggregate, DomainEvent):
409
- """
410
- Frozen data class representing aggregate events.
370
+ """Frozen data class representing aggregate events.
411
371
 
412
372
  Subclasses represent original decisions made by domain model aggregates.
413
373
  """
@@ -415,29 +375,22 @@ class AggregateEvent(CanMutateAggregate, DomainEvent):
415
375
 
416
376
  @dataclass(frozen=True)
417
377
  class AggregateCreated(CanInitAggregate, AggregateEvent):
418
- """
419
- Frozen data class representing the initial creation of an aggregate.
420
- """
378
+ """Frozen data class representing the initial creation of an aggregate."""
421
379
 
422
380
  originator_topic: str
423
381
  """String describing the path to an aggregate class."""
424
382
 
425
383
 
426
384
  class EventSourcingError(Exception):
427
- """
428
- Base exception class.
429
- """
385
+ """Base exception class."""
430
386
 
431
387
 
432
388
  class ProgrammingError(EventSourcingError):
433
- """
434
- Exception class for domain model programming errors.
435
- """
389
+ """Exception class for domain model programming errors."""
436
390
 
437
391
 
438
392
  class LogEvent(DomainEvent):
439
- """
440
- Deprecated: Inherit from DomainEvent instead.
393
+ """Deprecated: Inherit from DomainEvent instead.
441
394
 
442
395
  Base class for the events of event-sourced logs.
443
396
  """
@@ -459,16 +412,16 @@ def _spec_filter_kwargs_for_method_params(method: Callable[..., Any]) -> set[str
459
412
  if TYPE_CHECKING:
460
413
  EventSpecType = Union[str, type[CanMutateAggregate]]
461
414
 
462
- CommandMethod = Callable[..., None]
463
- DecoratedObjType = Union[CommandMethod, property]
464
- TDecoratedObjType = TypeVar("TDecoratedObjType", bound=DecoratedObjType)
415
+ CallableType = Callable[..., None]
416
+ DecoratableType = Union[CallableType, property]
417
+ TDecoratableType = TypeVar("TDecoratableType", bound=DecoratableType)
465
418
 
466
419
 
467
420
  class CommandMethodDecorator:
468
421
  def __init__(
469
422
  self,
470
423
  event_spec: EventSpecType | None,
471
- decorated_obj: DecoratedObjType,
424
+ decorated_obj: DecoratableType,
472
425
  ):
473
426
  self.is_name_inferred_from_method = False
474
427
  self.given_event_cls: type[CanMutateAggregate] | None = None
@@ -476,7 +429,7 @@ class CommandMethodDecorator:
476
429
  self.decorated_property: property | None = None
477
430
  self.is_property_setter = False
478
431
  self.property_setter_arg_name: str | None = None
479
- self.decorated_method: FunctionType | WrapperDescriptorType
432
+ self.decorated_func: CallableType
480
433
 
481
434
  # Event name has been specified.
482
435
  if isinstance(event_spec, str):
@@ -508,30 +461,34 @@ class CommandMethodDecorator:
508
461
  # Remember we are decorating a property.
509
462
  self.decorated_property = decorated_obj
510
463
 
511
- # Remember the decorated method as the "setter" of the property.
512
- self.decorated_method = cast(FunctionType, decorated_obj.fset)
513
-
514
- assert isinstance(self.decorated_method, FunctionType)
464
+ # TODO: Disallow unusual property setters in more detail.
465
+ assert isinstance(decorated_obj.fset, FunctionType)
515
466
 
516
467
  # Disallow deriving event class names from property names.
517
468
  if not self.given_event_cls and not self.event_cls_name:
518
- method_name = self.decorated_method.__name__
519
- msg = f"@event on {method_name}() setter requires event name or class"
469
+ method_name = decorated_obj.fset.__name__
470
+ msg = (
471
+ f"@event decorator on @{method_name}.setter "
472
+ f"requires event name or class"
473
+ )
520
474
  raise TypeError(msg)
521
475
 
476
+ # Remember property "setter" as the decorated function.
477
+ self.decorated_func = decorated_obj.fset
478
+
522
479
  # Remember the name of the second setter arg.
523
- setter_arg_names = list(inspect.signature(self.decorated_method).parameters)
480
+ setter_arg_names = list(inspect.signature(self.decorated_func).parameters)
524
481
  assert len(setter_arg_names) == 2
525
482
  self.property_setter_arg_name = setter_arg_names[1]
526
483
 
527
- # Process a decorated method.
484
+ # Process a decorated function.
528
485
  elif isinstance(decorated_obj, FunctionType):
529
- # Remember the decorated method as the decorated object.
530
- self.decorated_method = decorated_obj
486
+ # Remember the decorated obj as the decorated method.
487
+ self.decorated_func = decorated_obj
531
488
 
532
489
  # If necessary, derive an event class name from the method.
533
490
  if not self.given_event_cls and not self.event_cls_name:
534
- original_method_name = self.decorated_method.__name__
491
+ original_method_name = self.decorated_func.__name__
535
492
  if original_method_name != "__init__":
536
493
  self.is_name_inferred_from_method = True
537
494
  self.event_cls_name = "".join(
@@ -545,7 +502,11 @@ class CommandMethodDecorator:
545
502
 
546
503
  # Disallow using methods with variable params to define event class.
547
504
  if self.event_cls_name:
548
- _check_no_variable_params(self.decorated_method)
505
+ _raise_type_error_if_func_has_variable_params(self.decorated_func)
506
+
507
+ # Disallow using methods with positional only params to define event class.
508
+ if self.event_cls_name:
509
+ _raise_type_error_if_func_has_positional_only_params(self.decorated_func)
549
510
 
550
511
  def __call__(self, *args: Any, **kwargs: Any) -> None:
551
512
  # Initialised decorator was called directly, presumably by
@@ -564,7 +525,7 @@ class CommandMethodDecorator:
564
525
 
565
526
  @overload
566
527
  def __get__(
567
- self, instance: None, owner: MetaAggregate[Aggregate]
528
+ self, instance: None, owner: type[BaseAggregate]
568
529
  ) -> UnboundCommandMethodDecorator | property:
569
530
  """
570
531
  Descriptor protocol for getting decorated method or property on class object.
@@ -572,22 +533,24 @@ class CommandMethodDecorator:
572
533
 
573
534
  @overload
574
535
  def __get__(
575
- self, instance: Aggregate, owner: MetaAggregate[Aggregate]
536
+ self, instance: BaseAggregate, owner: type[BaseAggregate]
576
537
  ) -> BoundCommandMethodDecorator | Any:
577
538
  """
578
539
  Descriptor protocol for getting decorated method or property on instance object.
579
540
  """
580
541
 
581
542
  def __get__(
582
- self, instance: Aggregate | None, owner: MetaAggregate[Aggregate]
543
+ self, instance: BaseAggregate | None, owner: type[BaseAggregate]
583
544
  ) -> BoundCommandMethodDecorator | UnboundCommandMethodDecorator | property | Any:
584
- """
585
- Descriptor protocol for getting decorated method or property.
586
- """
545
+ """Descriptor protocol for getting decorated method or property."""
587
546
  # If we are decorating a property, then delegate to the property's __get__.
588
547
  if self.decorated_property:
589
548
  return self.decorated_property.__get__(instance, owner)
590
549
 
550
+ # If we are decorating an __init__ method, then delegate to the __init__ method.
551
+ if self.decorated_func.__name__ == "__init__":
552
+ return self.decorated_func.__get__(instance, owner)
553
+
591
554
  # Return a "bound" command method decorator if we have an instance.
592
555
  if instance:
593
556
  return BoundCommandMethodDecorator(self, instance)
@@ -595,10 +558,8 @@ class CommandMethodDecorator:
595
558
  # Return an "unbound" command method decorator if we have no instance.
596
559
  return UnboundCommandMethodDecorator(self)
597
560
 
598
- def __set__(self, instance: Aggregate, value: Any) -> None:
599
- """
600
- Descriptor protocol for assigning to decorated property.
601
- """
561
+ def __set__(self, instance: BaseAggregate, value: Any) -> None:
562
+ """Descriptor protocol for assigning to decorated property."""
602
563
  # Set decorated property indirectly by triggering an event.
603
564
  assert self.property_setter_arg_name
604
565
  b = BoundCommandMethodDecorator(self, instance)
@@ -607,35 +568,28 @@ class CommandMethodDecorator:
607
568
 
608
569
 
609
570
  @overload
610
- def event(arg: TDecoratedObjType) -> TDecoratedObjType:
611
- """
612
- Signature for calling ``@event`` decorator with decorated method.
613
- """
571
+ def event(arg: TDecoratableType) -> TDecoratableType:
572
+ """Signature for calling ``@event`` decorator with decorated method."""
614
573
 
615
574
 
616
575
  @overload
617
576
  def event(
618
577
  arg: EventSpecType,
619
- ) -> Callable[[TDecoratedObjType], TDecoratedObjType]:
620
- """
621
- Signature for calling ``@event`` decorator with event specification.
622
- """
578
+ ) -> Callable[[TDecoratableType], TDecoratableType]:
579
+ """Signature for calling ``@event`` decorator with event specification."""
623
580
 
624
581
 
625
582
  @overload
626
583
  def event(
627
584
  arg: None = None,
628
- ) -> Callable[[TDecoratedObjType], TDecoratedObjType]:
629
- """
630
- Signature for calling ``@event`` decorator without event specification.
631
- """
585
+ ) -> Callable[[TDecoratableType], TDecoratableType]:
586
+ """Signature for calling ``@event`` decorator without event specification."""
632
587
 
633
588
 
634
589
  def event(
635
- arg: EventSpecType | TDecoratedObjType | None = None,
636
- ) -> TDecoratedObjType | Callable[[TDecoratedObjType], TDecoratedObjType]:
637
- """
638
- Event-triggering decorator for aggregate command methods and property setters.
590
+ arg: EventSpecType | TDecoratableType | None = None,
591
+ ) -> TDecoratableType | Callable[[TDecoratableType], TDecoratableType]:
592
+ """Event-triggering decorator for aggregate command methods and property setters.
639
593
 
640
594
  Can be used to decorate an aggregate method or property setter so that an
641
595
  event will be triggered when the method is called or the property is set.
@@ -690,25 +644,24 @@ def event(
690
644
  decorated_obj=arg,
691
645
  )
692
646
  return cast(
693
- Callable[[TDecoratedObjType], TDecoratedObjType], command_method_decorator
647
+ "Callable[[TDecoratableType], TDecoratableType]", command_method_decorator
694
648
  )
695
649
 
696
650
  if (
697
651
  arg is None
698
652
  or isinstance(arg, str)
699
- or isinstance(arg, type)
700
- and issubclass(arg, CanMutateAggregate)
653
+ or (isinstance(arg, type) and issubclass(arg, CanMutateAggregate))
701
654
  ):
702
655
  event_spec = arg
703
656
 
704
657
  def create_command_method_decorator(
705
- decorated_obj: TDecoratedObjType,
706
- ) -> TDecoratedObjType:
658
+ decorated_obj: TDecoratableType,
659
+ ) -> TDecoratableType:
707
660
  command_method_decorator = CommandMethodDecorator(
708
661
  event_spec=event_spec,
709
662
  decorated_obj=decorated_obj,
710
663
  )
711
- return cast(TDecoratedObjType, command_method_decorator)
664
+ return cast("TDecoratableType", command_method_decorator)
712
665
 
713
666
  return create_command_method_decorator
714
667
 
@@ -723,21 +676,16 @@ triggers = event
723
676
 
724
677
 
725
678
  class UnboundCommandMethodDecorator:
726
- """
727
- Wraps a CommandMethodDecorator instance when accessed on an aggregate class.
728
- """
679
+ """Wraps a CommandMethodDecorator instance when accessed on an aggregate class."""
729
680
 
730
681
  def __init__(self, event_decorator: CommandMethodDecorator):
731
- """
732
-
733
- :param CommandMethodDecorator event_decorator:
734
- """
682
+ """:param CommandMethodDecorator event_decorator:"""
735
683
  self.event_decorator = event_decorator
736
- self.__module__ = event_decorator.decorated_method.__module__
737
- self.__name__ = event_decorator.decorated_method.__name__
738
- self.__qualname__ = event_decorator.decorated_method.__qualname__
739
- self.__annotations__ = event_decorator.decorated_method.__annotations__
740
- self.__doc__ = event_decorator.decorated_method.__doc__
684
+ self.__module__ = event_decorator.decorated_func.__module__
685
+ self.__name__ = event_decorator.decorated_func.__name__
686
+ self.__qualname__ = event_decorator.decorated_func.__qualname__
687
+ self.__annotations__ = event_decorator.decorated_func.__annotations__
688
+ self.__doc__ = event_decorator.decorated_func.__doc__
741
689
  # self.__wrapped__ = event_decorator.decorated_method
742
690
  # functools.update_wrapper(self, event_decorator.decorated_method)
743
691
 
@@ -754,28 +702,27 @@ class UnboundCommandMethodDecorator:
754
702
 
755
703
 
756
704
  class BoundCommandMethodDecorator:
757
- """
758
- Binds a CommandMethodDecorator with an aggregate instance so calls to
705
+ """Binds a CommandMethodDecorator with an aggregate instance so calls to
759
706
  decorated command methods can be intercepted and will trigger an event.
760
707
  """
761
708
 
762
- def __init__(self, event_decorator: CommandMethodDecorator, aggregate: Aggregate):
763
- """
764
-
765
- :param CommandMethodDecorator event_decorator:
709
+ def __init__(
710
+ self, event_decorator: CommandMethodDecorator, aggregate: BaseAggregate
711
+ ):
712
+ """:param CommandMethodDecorator event_decorator:
766
713
  :param Aggregate aggregate:
767
714
  """
768
715
  self.event_decorator = event_decorator
769
- self.__module__ = event_decorator.decorated_method.__module__
770
- self.__name__ = event_decorator.decorated_method.__name__
771
- self.__qualname__ = event_decorator.decorated_method.__qualname__
772
- self.__annotations__ = event_decorator.decorated_method.__annotations__
773
- self.__doc__ = event_decorator.decorated_method.__doc__
716
+ self.__module__ = event_decorator.decorated_func.__module__
717
+ self.__name__ = event_decorator.decorated_func.__name__
718
+ self.__qualname__ = event_decorator.decorated_func.__qualname__
719
+ self.__annotations__ = event_decorator.decorated_func.__annotations__
720
+ self.__doc__ = event_decorator.decorated_func.__doc__
774
721
  self.aggregate = aggregate
775
722
 
776
723
  def trigger(self, *args: Any, **kwargs: Any) -> None:
777
724
  kwargs = _coerce_args_to_kwargs(
778
- self.event_decorator.decorated_method, args, kwargs
725
+ self.event_decorator.decorated_func, args, kwargs
779
726
  )
780
727
  event_cls = decorator_event_classes[self.event_decorator]
781
728
  kwargs = _filter_kwargs_for_method_params(kwargs, event_cls)
@@ -786,32 +733,31 @@ class BoundCommandMethodDecorator:
786
733
 
787
734
 
788
735
  class DecoratorEvent(CanMutateAggregate):
789
- def apply(self, aggregate: Aggregate) -> None:
790
- """
791
- Applies event to aggregate by calling method decorated by @event.
792
- """
793
- # Call super method, just in case any base classes need it.
794
- super().apply(aggregate)
736
+ def apply(self, aggregate: BaseAggregate) -> None:
737
+ """Applies event to aggregate by calling method decorated by @event."""
738
+ # Identify the function that was decorated.
739
+ decorated_func = _decorated_funcs[type(self)]
795
740
 
796
- # Identify the method that was decorated.
797
- decorated_method = _decorated_methods[type(self)]
798
-
799
- # Select event attributes mentioned in method signature.
800
- kwargs = _filter_kwargs_for_method_params(self.__dict__, decorated_method)
741
+ # Select event attributes mentioned in function signature.
742
+ kwargs = _filter_kwargs_for_method_params(self.__dict__, decorated_func)
801
743
 
802
744
  # Call the original method with event attribute values.
803
- decorated_method(aggregate, **kwargs)
745
+ decorated_method = decorated_func.__get__(aggregate, type(aggregate))
746
+ decorated_method(**kwargs)
747
+
748
+ # Call super method, just in case any base classes need it.
749
+ super().apply(aggregate)
804
750
 
805
751
 
806
752
  _given_event_classes: set[type] = set()
807
- _decorated_methods: dict[type, CommandMethod] = {}
753
+ _decorated_funcs: dict[type, CallableType] = {}
808
754
  _created_event_classes: dict[type, list[type[CanInitAggregate]]] = {}
809
755
 
810
756
 
811
757
  decorator_event_classes: dict[CommandMethodDecorator, type[DecoratorEvent]] = {}
812
758
 
813
759
 
814
- def _check_no_variable_params(method: FunctionType) -> None:
760
+ def _raise_type_error_if_func_has_variable_params(method: CallableType) -> None:
815
761
  for param in inspect.signature(method).parameters.values():
816
762
  if param.kind is param.VAR_POSITIONAL:
817
763
  msg = f"*{param.name} not supported by decorator on {method.__name__}()"
@@ -826,14 +772,32 @@ def _check_no_variable_params(method: FunctionType) -> None:
826
772
  raise TypeError(msg)
827
773
 
828
774
 
775
+ def _raise_type_error_if_func_has_positional_only_params(method: CallableType) -> None:
776
+ # TODO: Support POSITIONAL_ONLY?
777
+ positional_only_params = []
778
+ for param in inspect.signature(method).parameters.values():
779
+ if param.name == "self":
780
+ continue
781
+ if param.kind is param.POSITIONAL_ONLY:
782
+ positional_only_params.append(param.name)
783
+
784
+ if positional_only_params:
785
+ msg = (
786
+ f"positional only args arg not supported by @event decorator on "
787
+ f"{method.__name__}(): {', '.join(positional_only_params)}"
788
+ )
789
+ raise TypeError(msg)
790
+
791
+
829
792
  def _coerce_args_to_kwargs(
830
- method: FunctionType | WrapperDescriptorType,
793
+ method: CallableType,
831
794
  args: Iterable[Any],
832
795
  kwargs: dict[str, Any],
833
796
  *,
834
797
  expects_id: bool = False,
835
798
  ) -> dict[str, Any]:
836
- assert isinstance(method, (FunctionType, WrapperDescriptorType))
799
+ # __init__ methods are WrapperDescriptorType, other method are FunctionType.
800
+ assert isinstance(method, (FunctionType, WrapperDescriptorType)), method
837
801
 
838
802
  args = tuple(args)
839
803
  enumerated_args_names, keyword_defaults_items = _spec_coerce_args_to_kwargs(
@@ -844,16 +808,14 @@ def _coerce_args_to_kwargs(
844
808
  )
845
809
 
846
810
  copy_kwargs = dict(kwargs)
847
- for i, name in enumerated_args_names:
848
- copy_kwargs[name] = args[i]
849
- for name, value in keyword_defaults_items:
850
- copy_kwargs[name] = value
811
+ copy_kwargs.update({name: args[i] for i, name in enumerated_args_names})
812
+ copy_kwargs.update(keyword_defaults_items)
851
813
  return copy_kwargs
852
814
 
853
815
 
854
816
  @cache
855
817
  def _spec_coerce_args_to_kwargs(
856
- method: FunctionType | WrapperDescriptorType,
818
+ method: CallableType,
857
819
  len_args: int,
858
820
  kwargs_keys: tuple[str],
859
821
  *,
@@ -907,16 +869,14 @@ def _spec_coerce_args_to_kwargs(
907
869
  f"argument{'' if num_missing == 1 else 's'}: "
908
870
  )
909
871
  _raise_missing_names_type_error(missing_names, msg)
910
- counter = 0
911
872
  args_names = []
912
- for name in positional_names:
873
+ for counter, name in enumerate(positional_names):
913
874
  if counter + 1 > len_args:
914
875
  break
915
876
  if name in kwargs_keys:
916
877
  msg = f"{method_name}() got multiple values for argument '{name}'"
917
878
  raise TypeError(msg)
918
879
  args_names.append(name)
919
- counter += 1
920
880
  missing_keyword_only_arguments = [
921
881
  name for name in required_keyword_only if name not in kwargs_keys
922
882
  ]
@@ -952,15 +912,13 @@ _create_id_param_names: dict[type[BaseAggregate], list[str]] = defaultdict(list)
952
912
 
953
913
 
954
914
  class MetaAggregate(EventsourcingType, Generic[TAggregate], type):
955
- """
956
- Metaclass for aggregate classes.
957
- """
915
+ """Metaclass for aggregate classes."""
958
916
 
959
917
  def _define_event_class(
960
918
  cls,
961
919
  name: str,
962
920
  bases: tuple[type[CanMutateAggregate], ...],
963
- apply_method: CommandMethod | None,
921
+ apply_method: CallableType | None,
964
922
  ) -> type[CanMutateAggregate]:
965
923
  # Define annotations for the event class (specs the init method).
966
924
  annotations = {}
@@ -985,7 +943,7 @@ class MetaAggregate(EventsourcingType, Generic[TAggregate], type):
985
943
  }
986
944
 
987
945
  # Create the event class object.
988
- return cast(type[CanMutateAggregate], type(name, bases, event_cls_dict))
946
+ return cast("type[CanMutateAggregate]", type(name, bases, event_cls_dict))
989
947
 
990
948
  def __call__(
991
949
  cls: MetaAggregate[TAggregate], *args: Any, **kwargs: Any
@@ -1001,9 +959,8 @@ class MetaAggregate(EventsourcingType, Generic[TAggregate], type):
1001
959
  )
1002
960
  raise TypeError(msg)
1003
961
 
1004
- cls_init: FunctionType | WrapperDescriptorType = cls.__init__ # type: ignore
1005
962
  kwargs = _coerce_args_to_kwargs(
1006
- cls_init,
963
+ cls.__init__, # type: ignore[misc]
1007
964
  args,
1008
965
  kwargs,
1009
966
  expects_id=cls in _annotations_mention_id,
@@ -1024,17 +981,13 @@ class MetaAggregate(EventsourcingType, Generic[TAggregate], type):
1024
981
 
1025
982
 
1026
983
  class BaseAggregate(metaclass=MetaAggregate):
1027
- """
1028
- Base class for aggregates.
1029
- """
984
+ """Base class for aggregates."""
1030
985
 
1031
986
  INITIAL_VERSION = 1
1032
987
 
1033
988
  @staticmethod
1034
989
  def create_id(*_: Any, **__: Any) -> UUID:
1035
- """
1036
- Returns a new aggregate ID.
1037
- """
990
+ """Returns a new aggregate ID."""
1038
991
  return uuid4()
1039
992
 
1040
993
  @classmethod
@@ -1045,9 +998,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1045
998
  id: UUID | None = None, # noqa: A002
1046
999
  **kwargs: Any,
1047
1000
  ) -> Self:
1048
- """
1049
- Constructs a new aggregate object instance.
1050
- """
1001
+ """Constructs a new aggregate object instance."""
1051
1002
  # Construct the domain event with an ID and a
1052
1003
  # version, and a topic for the aggregate class.
1053
1004
  create_id_kwargs = {
@@ -1083,19 +1034,18 @@ class BaseAggregate(metaclass=MetaAggregate):
1083
1034
  msg = f"Unable to construct '{event_class.__qualname__}' event: {e}"
1084
1035
  raise TypeError(msg) from e
1085
1036
  # Construct the aggregate object.
1086
- agg = cast(Self, created_event.mutate(None))
1037
+ agg = cast("Self", created_event.mutate(None))
1087
1038
 
1088
1039
  assert agg is not None
1089
1040
  # Append the domain event to pending list.
1090
- agg._pending_events.append(created_event)
1041
+ agg.pending_events.append(created_event)
1091
1042
  # Return the aggregate.
1092
1043
  return agg
1093
1044
 
1094
1045
  def __base_init__(
1095
1046
  self, originator_id: UUID, originator_version: int, timestamp: datetime
1096
1047
  ) -> None:
1097
- """
1098
- Initialises an aggregate object with an :data:`id`, a :data:`version`
1048
+ """Initialises an aggregate object with an :data:`id`, a :data:`version`
1099
1049
  number, and a :data:`timestamp`.
1100
1050
  """
1101
1051
  self._id = originator_id
@@ -1106,16 +1056,12 @@ class BaseAggregate(metaclass=MetaAggregate):
1106
1056
 
1107
1057
  @property
1108
1058
  def id(self) -> UUID:
1109
- """
1110
- The ID of the aggregate.
1111
- """
1059
+ """The ID of the aggregate."""
1112
1060
  return self._id
1113
1061
 
1114
1062
  @property
1115
1063
  def version(self) -> int:
1116
- """
1117
- The version number of the aggregate.
1118
- """
1064
+ """The version number of the aggregate."""
1119
1065
  return self._version
1120
1066
 
1121
1067
  @version.setter
@@ -1124,16 +1070,12 @@ class BaseAggregate(metaclass=MetaAggregate):
1124
1070
 
1125
1071
  @property
1126
1072
  def created_on(self) -> datetime:
1127
- """
1128
- The date and time when the aggregate was created.
1129
- """
1073
+ """The date and time when the aggregate was created."""
1130
1074
  return self._created_on
1131
1075
 
1132
1076
  @property
1133
1077
  def modified_on(self) -> datetime:
1134
- """
1135
- The date and time when the aggregate was last modified.
1136
- """
1078
+ """The date and time when the aggregate was last modified."""
1137
1079
  return self._modified_on
1138
1080
 
1139
1081
  @modified_on.setter
@@ -1142,9 +1084,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1142
1084
 
1143
1085
  @property
1144
1086
  def pending_events(self) -> list[CanMutateAggregate]:
1145
- """
1146
- A list of pending events.
1147
- """
1087
+ """A list of pending events."""
1148
1088
  return self._pending_events
1149
1089
 
1150
1090
  def trigger_event(
@@ -1152,8 +1092,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1152
1092
  event_class: type[CanMutateAggregate],
1153
1093
  **kwargs: Any,
1154
1094
  ) -> None:
1155
- """
1156
- Triggers domain event of given type, by creating
1095
+ """Triggers domain event of given type, by creating
1157
1096
  an event object and using it to mutate the aggregate.
1158
1097
  """
1159
1098
  # Construct the domain event as the
@@ -1182,8 +1121,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1182
1121
  self._pending_events.append(new_event)
1183
1122
 
1184
1123
  def collect_events(self) -> Sequence[CanMutateAggregate]:
1185
- """
1186
- Collects and returns a list of pending aggregate
1124
+ """Collects and returns a list of pending aggregate
1187
1125
  :class:`AggregateEvent` objects.
1188
1126
  """
1189
1127
  collected = []
@@ -1238,7 +1176,6 @@ class BaseAggregate(metaclass=MetaAggregate):
1238
1176
  setattr(cls, base_event_name, base_event_cls)
1239
1177
 
1240
1178
  # Make sure all events defined on aggregate subclass the base event class.
1241
- created_event_classes: dict[str, type[CanInitAggregate]] = {}
1242
1179
  for name, value in tuple(cls.__dict__.items()):
1243
1180
  if name == base_event_name:
1244
1181
  # Don't subclass the base event class again.
@@ -1253,19 +1190,30 @@ class BaseAggregate(metaclass=MetaAggregate):
1253
1190
  ):
1254
1191
  sub_class = cls._define_event_class(name, (value, base_event_cls), None)
1255
1192
  setattr(cls, name, sub_class)
1256
- for name, value in tuple(cls.__dict__.items()):
1257
- if isinstance(value, type) and issubclass(value, CanInitAggregate):
1258
- created_event_classes[name] = value
1193
+ created_event_classes: dict[str, type[CanInitAggregate]] = {
1194
+ name: value
1195
+ for name, value in cls.__dict__.items()
1196
+ if isinstance(value, type) and issubclass(value, CanInitAggregate)
1197
+ }
1198
+ # Analyse the __init__ attribute.
1199
+ init_attr: FunctionType | CommandMethodDecorator | None = cls.__dict__.get(
1200
+ "__init__"
1201
+ )
1202
+ if init_attr is None:
1203
+ init_decorator: CommandMethodDecorator | None = None
1204
+ init_method: CallableType | None = None
1205
+ elif isinstance(init_attr, CommandMethodDecorator):
1206
+ init_decorator = init_attr
1207
+ init_method = init_attr.decorated_func
1208
+ else:
1209
+ init_decorator = None
1210
+ init_method = init_attr
1259
1211
 
1260
1212
  # Identify or define the aggregate's "created" event class.
1261
1213
  created_event_class: type[CanInitAggregate] | None = None
1262
1214
 
1263
- # Is the init method decorated with a CommandMethodDecorator?
1264
- if isinstance(cls.__dict__.get("__init__"), CommandMethodDecorator):
1265
- init_decorator: CommandMethodDecorator = cls.__dict__["__init__"]
1266
-
1267
- # Set the original method on the class (un-decorate __init__).
1268
- cls.__init__ = init_decorator.decorated_method # type: ignore
1215
+ # Does class have an init method decorated with a CommandMethodDecorator?
1216
+ if init_decorator:
1269
1217
 
1270
1218
  # Disallow using both 'created_event_name' and decorator on __init__.
1271
1219
  if created_event_name:
@@ -1275,7 +1223,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1275
1223
  # Does the decorator specify a "created" event class?
1276
1224
  if init_decorator.given_event_cls:
1277
1225
  created_event_class = cast(
1278
- type[CanInitAggregate], init_decorator.given_event_cls
1226
+ "type[CanInitAggregate]", init_decorator.given_event_cls
1279
1227
  )
1280
1228
 
1281
1229
  # Does the decorator specify a "created" event name?
@@ -1287,7 +1235,6 @@ class BaseAggregate(metaclass=MetaAggregate):
1287
1235
  msg = "Decorator on __init__ has neither event name nor class"
1288
1236
  raise TypeError(msg)
1289
1237
 
1290
- # TODO: Write a test to cover this when "Created" class is explicitly defined.
1291
1238
  # Check if init mentions ID.
1292
1239
  for param_name in inspect.signature(cls.__init__).parameters:
1293
1240
  if param_name == "id":
@@ -1323,7 +1270,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1323
1270
  if created_event_name and len(created_event_classes) == 1:
1324
1271
  base_created_event_cls = next(iter(created_event_classes.values()))
1325
1272
  else:
1326
- # Todo: This could probably be improved.
1273
+ # TODO: This could probably be improved.
1327
1274
  # Look for first class in MRO that has one specified "created" class.
1328
1275
  for base_cls in cls.__mro__:
1329
1276
  if (
@@ -1333,7 +1280,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1333
1280
  base_created_event_cls = _created_event_classes[base_cls][0]
1334
1281
  break
1335
1282
  else: # pragma: no cover
1336
- # Todo: Write a test to cover this.
1283
+ # TODO: Write a test to cover this.
1337
1284
  msg = (
1338
1285
  "Can't find base aggregate class with "
1339
1286
  "a specified 'created' event class"
@@ -1345,15 +1292,8 @@ class BaseAggregate(metaclass=MetaAggregate):
1345
1292
 
1346
1293
  # Disallow init method from having variable params if
1347
1294
  # we are using it to define a "created" event class.
1348
- try:
1349
- init_method = cls.__dict__["__init__"]
1350
- except KeyError:
1351
- init_method = None
1352
- else:
1353
- try:
1354
- _check_no_variable_params(init_method)
1355
- except TypeError:
1356
- raise
1295
+ if init_method:
1296
+ _raise_type_error_if_func_has_variable_params(init_method)
1357
1297
 
1358
1298
  # Define a "created" event class for this aggregate.
1359
1299
  if issubclass(base_created_event_cls, base_event_cls):
@@ -1362,7 +1302,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1362
1302
  else:
1363
1303
  bases = (base_created_event_cls, base_event_cls)
1364
1304
  created_event_class = cast(
1365
- type[CanInitAggregate],
1305
+ "type[CanInitAggregate]",
1366
1306
  cls._define_event_class(
1367
1307
  created_event_name,
1368
1308
  bases,
@@ -1384,20 +1324,22 @@ class BaseAggregate(metaclass=MetaAggregate):
1384
1324
 
1385
1325
  if isinstance(attr_value, CommandMethodDecorator):
1386
1326
  event_decorator = attr_value
1327
+ if event_decorator.decorated_func.__name__ == "__init__":
1328
+ continue
1387
1329
 
1388
1330
  elif isinstance(attr_value, property) and isinstance(
1389
1331
  attr_value.fset, CommandMethodDecorator
1390
1332
  ):
1391
1333
  event_decorator = attr_value.fset
1392
1334
  # Inspect the setter method.
1393
- method_signature = inspect.signature(event_decorator.decorated_method)
1335
+ method_signature = inspect.signature(event_decorator.decorated_func)
1394
1336
  assert len(method_signature.parameters) == 2
1395
1337
  event_decorator.is_property_setter = True
1396
1338
  event_decorator.property_setter_arg_name = list(
1397
1339
  method_signature.parameters
1398
1340
  )[1]
1399
- if event_decorator.decorated_method.__name__ != attr_name:
1400
- attr = cls.__dict__[event_decorator.decorated_method.__name__]
1341
+ if event_decorator.decorated_func.__name__ != attr_name:
1342
+ attr = cls.__dict__[event_decorator.decorated_func.__name__]
1401
1343
  if isinstance(attr, CommandMethodDecorator):
1402
1344
  # This is the "x = property(getx, setx) form" where setx
1403
1345
  # is a decorated method.
@@ -1406,10 +1348,10 @@ class BaseAggregate(metaclass=MetaAggregate):
1406
1348
  elif event_decorator.is_name_inferred_from_method:
1407
1349
  # This is the "@property.setter \ @event" form. We don't want
1408
1350
  # event class name inferred from property (not past participle).
1409
- method_name = event_decorator.decorated_method.__name__
1351
+ method_name = event_decorator.decorated_func.__name__
1410
1352
  msg = (
1411
- f"@event under {method_name}() property setter requires "
1412
- "event class name"
1353
+ f"@event decorator under @{method_name}.setter "
1354
+ "requires event name or class"
1413
1355
  )
1414
1356
  raise TypeError(msg)
1415
1357
 
@@ -1425,7 +1367,7 @@ class BaseAggregate(metaclass=MetaAggregate):
1425
1367
 
1426
1368
  # Define event class as subclass of given class.
1427
1369
  given_subclass = cast(
1428
- type[CanMutateAggregate],
1370
+ "type[CanMutateAggregate]",
1429
1371
  getattr(cls, event_decorator.given_event_cls.__name__),
1430
1372
  )
1431
1373
  event_cls = cls._define_event_class(
@@ -1448,18 +1390,18 @@ class BaseAggregate(metaclass=MetaAggregate):
1448
1390
  event_cls = cls._define_event_class(
1449
1391
  event_decorator.event_cls_name,
1450
1392
  (DecoratorEvent, base_event_cls),
1451
- event_decorator.decorated_method,
1393
+ event_decorator.decorated_func,
1452
1394
  )
1453
1395
 
1454
1396
  # Cache the decorated method for the event class to use.
1455
- _decorated_methods[event_cls] = event_decorator.decorated_method
1397
+ _decorated_funcs[event_cls] = event_decorator.decorated_func
1456
1398
 
1457
1399
  # Set the event class as an attribute of the aggregate class.
1458
1400
  setattr(cls, event_cls.__name__, event_cls)
1459
1401
 
1460
1402
  # Remember which event class to trigger.
1461
1403
  decorator_event_classes[event_decorator] = cast(
1462
- type[DecoratorEvent], event_cls
1404
+ "type[DecoratorEvent]", event_cls
1463
1405
  )
1464
1406
 
1465
1407
  # Check any create_id method defined on this class is static or class method.
@@ -1515,8 +1457,7 @@ def aggregate(
1515
1457
  *,
1516
1458
  created_event_name: str = "",
1517
1459
  ) -> type[Aggregate] | Callable[[Any], type[Aggregate]]:
1518
- """
1519
- Converts the class that was passed in to inherit from Aggregate.
1460
+ """Converts the class that was passed in to inherit from Aggregate.
1520
1461
 
1521
1462
  .. code-block:: python
1522
1463
 
@@ -1558,8 +1499,7 @@ def aggregate(
1558
1499
 
1559
1500
 
1560
1501
  class OriginatorIDError(EventSourcingError):
1561
- """
1562
- Raised when a domain event can't be applied to
1502
+ """Raised when a domain event can't be applied to
1563
1503
  an aggregate due to an ID mismatch indicating
1564
1504
  the domain event is not in the aggregate's
1565
1505
  sequence of events.
@@ -1567,38 +1507,23 @@ class OriginatorIDError(EventSourcingError):
1567
1507
 
1568
1508
 
1569
1509
  class OriginatorVersionError(EventSourcingError):
1570
- """
1571
- Raised when a domain event can't be applied to
1510
+ """Raised when a domain event can't be applied to
1572
1511
  an aggregate due to version mismatch indicating
1573
1512
  the domain event is not the next in the aggregate's
1574
1513
  sequence of events.
1575
1514
  """
1576
1515
 
1577
1516
 
1578
- class VersionError(OriginatorVersionError):
1579
- """
1580
- Old name for 'OriginatorVersionError'.
1581
-
1582
- This class exists to maintain backwards-compatibility
1583
- but will be removed in a future version Please use
1584
- 'OriginatorVersionError' instead.
1585
- """
1586
-
1587
-
1588
1517
  class SnapshotProtocol(DomainEventProtocol, Protocol):
1589
1518
  @property
1590
1519
  def state(self) -> dict[str, Any]:
1591
- """
1592
- Snapshots have a read-only 'state'.
1593
- """
1520
+ """Snapshots have a read-only 'state'."""
1594
1521
  raise NotImplementedError # pragma: no cover
1595
1522
 
1596
1523
  # TODO: Improve on this 'Any'.
1597
1524
  @classmethod
1598
1525
  def take(cls: Any, aggregate: Any) -> Any:
1599
- """
1600
- Snapshots have a 'take()' class method.
1601
- """
1526
+ """Snapshots have a 'take()' class method."""
1602
1527
 
1603
1528
 
1604
1529
  TCanSnapshotAggregate = TypeVar("TCanSnapshotAggregate", bound="CanSnapshotAggregate")
@@ -1620,12 +1545,10 @@ class CanSnapshotAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
1620
1545
 
1621
1546
  @classmethod
1622
1547
  def take(
1623
- cls: type[TCanSnapshotAggregate],
1548
+ cls,
1624
1549
  aggregate: MutableOrImmutableAggregate,
1625
- ) -> TCanSnapshotAggregate:
1626
- """
1627
- Creates a snapshot of the given :class:`Aggregate` object.
1628
- """
1550
+ ) -> Self:
1551
+ """Creates a snapshot of the given :class:`Aggregate` object."""
1629
1552
  aggregate_state = dict(aggregate.__dict__)
1630
1553
  class_version = getattr(type(aggregate), "class_version", 1)
1631
1554
  if class_version > 1:
@@ -1643,10 +1566,8 @@ class CanSnapshotAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
1643
1566
  )
1644
1567
 
1645
1568
  def mutate(self, _: None) -> Aggregate:
1646
- """
1647
- Reconstructs the snapshotted :class:`Aggregate` object.
1648
- """
1649
- cls = cast(type[Aggregate], resolve_topic(self.topic))
1569
+ """Reconstructs the snapshotted :class:`Aggregate` object."""
1570
+ cls = cast("type[Aggregate]", resolve_topic(self.topic))
1650
1571
  aggregate_state = dict(self.state)
1651
1572
  from_version = aggregate_state.pop("class_version", 1)
1652
1573
  class_version = getattr(cls, "class_version", 1)
@@ -1666,8 +1587,7 @@ class CanSnapshotAggregate(HasOriginatorIDVersion, CanCreateTimestamp):
1666
1587
 
1667
1588
  @dataclass(frozen=True)
1668
1589
  class Snapshot(CanSnapshotAggregate, DomainEvent):
1669
- """
1670
- Snapshots represent the state of an aggregate at a particular
1590
+ """Snapshots represent the state of an aggregate at a particular
1671
1591
  version.
1672
1592
 
1673
1593
  Constructor arguments: