eventsourcing 9.3.4__py3-none-any.whl → 9.4.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.

@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import os
4
5
  from abc import ABC, abstractmethod
6
+ from collections.abc import Iterable, Iterator, Sequence
5
7
  from copy import deepcopy
6
8
  from dataclasses import dataclass
7
9
  from itertools import chain
@@ -11,22 +13,13 @@ from typing import (
11
13
  Any,
12
14
  Callable,
13
15
  ClassVar,
14
- Dict,
15
16
  Generic,
16
- Iterable,
17
- Iterator,
18
- List,
19
17
  Optional,
20
- Sequence,
21
- Tuple,
22
- Type,
23
18
  TypeVar,
24
19
  cast,
25
20
  )
26
21
  from warnings import warn
27
22
 
28
- from typing_extensions import deprecated
29
-
30
23
  from eventsourcing.domain import (
31
24
  Aggregate,
32
25
  CanMutateProtocol,
@@ -34,12 +27,12 @@ from eventsourcing.domain import (
34
27
  DomainEventProtocol,
35
28
  EventSourcingError,
36
29
  MutableOrImmutableAggregate,
37
- ProgrammingError,
30
+ SDomainEvent,
38
31
  Snapshot,
39
32
  SnapshotProtocol,
40
33
  TDomainEvent,
41
34
  TMutableOrImmutableAggregate,
42
- create_utc_datetime_now,
35
+ datetime_now_with_tzinfo,
43
36
  )
44
37
  from eventsourcing.persistence import (
45
38
  ApplicationRecorder,
@@ -47,6 +40,7 @@ from eventsourcing.persistence import (
47
40
  DecimalAsStr,
48
41
  EventStore,
49
42
  InfrastructureFactory,
43
+ JSONTranscoder,
50
44
  Mapper,
51
45
  Notification,
52
46
  Recording,
@@ -56,7 +50,7 @@ from eventsourcing.persistence import (
56
50
  )
57
51
  from eventsourcing.utils import Environment, EnvType, strtobool
58
52
 
59
- if TYPE_CHECKING: # pragma: nocover
53
+ if TYPE_CHECKING:
60
54
  from uuid import UUID
61
55
 
62
56
  ProjectorFunction = Callable[
@@ -70,12 +64,15 @@ MutatorFunction = Callable[
70
64
  ]
71
65
 
72
66
 
67
+ class ProgrammingError(Exception):
68
+ pass
69
+
70
+
73
71
  def project_aggregate(
74
72
  aggregate: TMutableOrImmutableAggregate | None,
75
73
  domain_events: Iterable[DomainEventProtocol],
76
74
  ) -> TMutableOrImmutableAggregate | None:
77
- """
78
- Projector function for aggregate projections, which works
75
+ """Projector function for aggregate projections, which works
79
76
  by successively calling aggregate mutator function mutate()
80
77
  on each of the given list of domain events in turn.
81
78
  """
@@ -91,22 +88,20 @@ T = TypeVar("T")
91
88
 
92
89
  class Cache(Generic[S, T]):
93
90
  def __init__(self) -> None:
94
- self.cache: Dict[S, Any] = {}
91
+ self.cache: dict[S, Any] = {}
95
92
 
96
93
  def get(self, key: S, *, evict: bool = False) -> T:
97
94
  if evict:
98
95
  return self.cache.pop(key)
99
96
  return self.cache[key]
100
97
 
101
- def put(self, key: S, value: T) -> T | None:
98
+ def put(self, key: S, value: T | None) -> None:
102
99
  if value is not None:
103
100
  self.cache[key] = value
104
- return None
105
101
 
106
102
 
107
103
  class LRUCache(Cache[S, T]):
108
- """
109
- Size limited caching that tracks accesses by recency.
104
+ """Size limited caching that tracks accesses by recency.
110
105
 
111
106
  This is basically copied from functools.lru_cache. But
112
107
  we need to know when there was a cache hit, so we can
@@ -122,7 +117,7 @@ class LRUCache(Cache[S, T]):
122
117
  self.maxsize = maxsize
123
118
  self.full = False
124
119
  self.lock = Lock() # because linkedlist updates aren't threadsafe
125
- self.root: List[Any] = [] # root of the circular doubly linked list
120
+ self.root: list[Any] = [] # root of the circular doubly linked list
126
121
  self.clear()
127
122
 
128
123
  def clear(self) -> None:
@@ -156,7 +151,7 @@ class LRUCache(Cache[S, T]):
156
151
  return result
157
152
  raise KeyError
158
153
 
159
- def put(self, key: S, value: T) -> Any | None:
154
+ def put(self, key: S, value: T | None) -> Any | None:
160
155
  evicted_key = None
161
156
  evicted_value = None
162
157
  with self.lock:
@@ -208,7 +203,8 @@ class Repository:
208
203
  """Reconstructs aggregates from events in an
209
204
  :class:`~eventsourcing.persistence.EventStore`,
210
205
  possibly using snapshot store to avoid replaying
211
- all events."""
206
+ all events.
207
+ """
212
208
 
213
209
  FASTFORWARD_LOCKS_CACHE_MAXSIZE = 50
214
210
 
@@ -222,8 +218,7 @@ class Repository:
222
218
  fastforward_skipping: bool = False,
223
219
  deepcopy_from_cache: bool = True,
224
220
  ):
225
- """
226
- Initialises repository with given event store (an
221
+ """Initialises repository with given event store (an
227
222
  :class:`~eventsourcing.persistence.EventStore` for aggregate
228
223
  :class:`~eventsourcing.domain.AggregateEvent` objects)
229
224
  and optionally a snapshot store (an
@@ -248,7 +243,7 @@ class Repository:
248
243
  self._fastforward_locks_cache: LRUCache[UUID, Lock] = LRUCache(
249
244
  maxsize=self.FASTFORWARD_LOCKS_CACHE_MAXSIZE
250
245
  )
251
- self._fastforward_locks_inuse: Dict[UUID, Tuple[Lock, int]] = {}
246
+ self._fastforward_locks_inuse: dict[UUID, tuple[Lock, int]] = {}
252
247
 
253
248
  def get(
254
249
  self,
@@ -261,15 +256,14 @@ class Repository:
261
256
  fastforward_skipping: bool = False,
262
257
  deepcopy_from_cache: bool = True,
263
258
  ) -> TMutableOrImmutableAggregate:
264
- """
265
- Reconstructs an :class:`~eventsourcing.domain.Aggregate` for a
259
+ """Reconstructs an :class:`~eventsourcing.domain.Aggregate` for a
266
260
  given ID from stored events, optionally at a particular version.
267
261
  """
268
262
  if self.cache and version is None:
269
263
  try:
270
264
  # Look for aggregate in the cache.
271
265
  aggregate = cast(
272
- TMutableOrImmutableAggregate, self.cache.get(aggregate_id)
266
+ "TMutableOrImmutableAggregate", self.cache.get(aggregate_id)
273
267
  )
274
268
  except KeyError:
275
269
  # Reconstruct aggregate from stored events.
@@ -290,7 +284,11 @@ class Repository:
290
284
  originator_id=aggregate_id, gt=aggregate.version
291
285
  )
292
286
  _aggregate = projector_func(
293
- aggregate, cast(Iterable[TDomainEvent], new_events)
287
+ aggregate,
288
+ cast(
289
+ "Iterable[TDomainEvent]",
290
+ new_events,
291
+ ),
294
292
  )
295
293
  if _aggregate is None:
296
294
  raise AggregateNotFoundError(aggregate_id)
@@ -345,8 +343,8 @@ class Repository:
345
343
  aggregate = projector_func(
346
344
  initial,
347
345
  chain(
348
- cast(Iterable[TDomainEvent], snapshots),
349
- cast(Iterable[TDomainEvent], aggregate_events),
346
+ cast("Iterable[TDomainEvent]", snapshots),
347
+ cast("Iterable[TDomainEvent]", aggregate_events),
350
348
  ),
351
349
  )
352
350
 
@@ -357,19 +355,18 @@ class Repository:
357
355
  return aggregate
358
356
 
359
357
  def _use_fastforward_lock(self, aggregate_id: UUID) -> Lock:
358
+ lock: Lock | None = None
360
359
  with self._fastforward_locks_lock:
361
- try:
360
+ num_users = 0
361
+ with contextlib.suppress(KeyError):
362
362
  lock, num_users = self._fastforward_locks_inuse[aggregate_id]
363
- except KeyError:
364
- try:
363
+ if lock is None:
364
+ with contextlib.suppress(KeyError):
365
365
  lock = self._fastforward_locks_cache.get(aggregate_id, evict=True)
366
- except KeyError:
367
- lock = Lock()
368
- finally:
369
- num_users = 0
370
- finally:
371
- num_users += 1
372
- self._fastforward_locks_inuse[aggregate_id] = (lock, num_users)
366
+ if lock is None:
367
+ lock = Lock()
368
+ num_users += 1
369
+ self._fastforward_locks_inuse[aggregate_id] = (lock, num_users)
373
370
  return lock
374
371
 
375
372
  def _disuse_fastforward_lock(self, aggregate_id: UUID) -> None:
@@ -383,9 +380,7 @@ class Repository:
383
380
  self._fastforward_locks_inuse[aggregate_id] = (lock_, num_users)
384
381
 
385
382
  def __contains__(self, item: UUID) -> bool:
386
- """
387
- Tests to see if an aggregate exists in the repository.
388
- """
383
+ """Tests to see if an aggregate exists in the repository."""
389
384
  try:
390
385
  self.get(aggregate_id=item)
391
386
  except AggregateNotFoundError:
@@ -396,8 +391,7 @@ class Repository:
396
391
 
397
392
  @dataclass(frozen=True)
398
393
  class Section:
399
- """
400
- Frozen dataclass that represents a section from a :class:`NotificationLog`.
394
+ """Frozen dataclass that represents a section from a :class:`NotificationLog`.
401
395
  The :data:`items` attribute contains a list of
402
396
  :class:`~eventsourcing.persistence.Notification` objects.
403
397
  The :data:`id` attribute is the section ID, two integers
@@ -410,24 +404,21 @@ class Section:
410
404
  Constructor arguments:
411
405
 
412
406
  :param Optional[str] id: section ID of this section e.g. "1,10"
413
- :param List[Notification] items: a list of event notifications
407
+ :param list[Notification] items: a list of event notifications
414
408
  :param Optional[str] next_id: section ID of the following section
415
409
  """
416
410
 
417
411
  id: str | None
418
- items: List[Notification]
412
+ items: list[Notification]
419
413
  next_id: str | None
420
414
 
421
415
 
422
416
  class NotificationLog(ABC):
423
- """
424
- Abstract base class for notification logs.
425
- """
417
+ """Abstract base class for notification logs."""
426
418
 
427
419
  @abstractmethod
428
420
  def __getitem__(self, section_id: str) -> Section:
429
- """
430
- Returns a :class:`Section` of
421
+ """Returns a :class:`Section` of
431
422
  :class:`~eventsourcing.persistence.Notification` objects
432
423
  from the notification log.
433
424
  """
@@ -435,21 +426,21 @@ class NotificationLog(ABC):
435
426
  @abstractmethod
436
427
  def select(
437
428
  self,
438
- start: int,
429
+ start: int | None,
439
430
  limit: int,
440
431
  stop: int | None = None,
441
432
  topics: Sequence[str] = (),
442
- ) -> List[Notification]:
443
- """
444
- Returns a selection of
433
+ *,
434
+ inclusive_of_start: bool = True,
435
+ ) -> list[Notification]:
436
+ """Returns a selection of
445
437
  :class:`~eventsourcing.persistence.Notification` objects
446
438
  from the notification log.
447
439
  """
448
440
 
449
441
 
450
442
  class LocalNotificationLog(NotificationLog):
451
- """
452
- Notification log that presents sections of event notifications
443
+ """Notification log that presents sections of event notifications
453
444
  retrieved from an :class:`~eventsourcing.persistence.ApplicationRecorder`.
454
445
  """
455
446
 
@@ -460,8 +451,7 @@ class LocalNotificationLog(NotificationLog):
460
451
  recorder: ApplicationRecorder,
461
452
  section_size: int = DEFAULT_SECTION_SIZE,
462
453
  ):
463
- """
464
- Initialises a local notification object with given
454
+ """Initialises a local notification object with given
465
455
  :class:`~eventsourcing.persistence.ApplicationRecorder`
466
456
  and an optional section size.
467
457
 
@@ -476,8 +466,7 @@ class LocalNotificationLog(NotificationLog):
476
466
  self.section_size = section_size
477
467
 
478
468
  def __getitem__(self, requested_section_id: str) -> Section:
479
- """
480
- Returns a :class:`Section` of event notifications
469
+ """Returns a :class:`Section` of event notifications
481
470
  based on the requested section ID. The section ID of
482
471
  the returned section will describe the event
483
472
  notifications that are actually contained in
@@ -523,13 +512,14 @@ class LocalNotificationLog(NotificationLog):
523
512
 
524
513
  def select(
525
514
  self,
526
- start: int,
515
+ start: int | None,
527
516
  limit: int,
528
517
  stop: int | None = None,
529
518
  topics: Sequence[str] = (),
530
- ) -> List[Notification]:
531
- """
532
- Returns a selection of
519
+ *,
520
+ inclusive_of_start: bool = True,
521
+ ) -> list[Notification]:
522
+ """Returns a selection of
533
523
  :class:`~eventsourcing.persistence.Notification` objects
534
524
  from the notification log.
535
525
  """
@@ -539,7 +529,11 @@ class LocalNotificationLog(NotificationLog):
539
529
  )
540
530
  raise ValueError(msg)
541
531
  return self.recorder.select_notifications(
542
- start=start, limit=limit, stop=stop, topics=topics
532
+ start=start,
533
+ limit=limit,
534
+ stop=stop,
535
+ topics=topics,
536
+ inclusive_of_start=inclusive_of_start,
543
537
  )
544
538
 
545
539
  @staticmethod
@@ -548,30 +542,25 @@ class LocalNotificationLog(NotificationLog):
548
542
 
549
543
 
550
544
  class ProcessingEvent:
551
- """
552
- Keeps together a :class:`~eventsourcing.persistence.Tracking`
545
+ """Keeps together a :class:`~eventsourcing.persistence.Tracking`
553
546
  object, which represents the position of a domain event notification
554
547
  in the notification log of a particular application, and the
555
548
  new domain events that result from processing that notification.
556
549
  """
557
550
 
558
551
  def __init__(self, tracking: Tracking | None = None):
559
- """
560
- Initialises the process event with the given tracking object.
561
- """
552
+ """Initialises the process event with the given tracking object."""
562
553
  self.tracking = tracking
563
- self.events: List[DomainEventProtocol] = []
564
- self.aggregates: Dict[UUID, MutableOrImmutableAggregate] = {}
565
- self.saved_kwargs: Dict[Any, Any] = {}
554
+ self.events: list[DomainEventProtocol] = []
555
+ self.aggregates: dict[UUID, MutableOrImmutableAggregate] = {}
556
+ self.saved_kwargs: dict[Any, Any] = {}
566
557
 
567
558
  def collect_events(
568
559
  self,
569
560
  *objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
570
561
  **kwargs: Any,
571
562
  ) -> None:
572
- """
573
- Collects pending domain events from the given aggregate.
574
- """
563
+ """Collects pending domain events from the given aggregate."""
575
564
  for obj in objs:
576
565
  if obj is None:
577
566
  continue
@@ -599,33 +588,17 @@ class ProcessingEvent:
599
588
  self.collect_events(*aggregates, **kwargs)
600
589
 
601
590
 
602
- class RecordingEvent:
603
- def __init__(
604
- self,
605
- application_name: str,
606
- recordings: List[Recording],
607
- previous_max_notification_id: int | None,
608
- ):
609
- self.application_name = application_name
610
- self.recordings = recordings
611
- self.previous_max_notification_id = previous_max_notification_id
612
-
613
-
614
591
  class Application:
615
- """
616
- Base class for event-sourced applications.
617
- """
592
+ """Base class for event-sourced applications."""
618
593
 
619
594
  name = "Application"
620
- env: ClassVar[Dict[str, str]] = {}
595
+ env: ClassVar[dict[str, str]] = {}
621
596
  is_snapshotting_enabled: bool = False
622
- snapshotting_intervals: ClassVar[
623
- Dict[Type[MutableOrImmutableAggregate], int] | None
624
- ] = None
597
+ snapshotting_intervals: ClassVar[dict[type[MutableOrImmutableAggregate], int]] = {}
625
598
  snapshotting_projectors: ClassVar[
626
- Dict[Type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]] | None
627
- ] = None
628
- snapshot_class: Type[SnapshotProtocol] = Snapshot
599
+ dict[type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]]
600
+ ] = {}
601
+ snapshot_class: type[SnapshotProtocol] = Snapshot
629
602
  log_section_size = 10
630
603
  notify_topics: Sequence[str] = []
631
604
 
@@ -639,8 +612,7 @@ class Application:
639
612
  cls.name = cls.__name__
640
613
 
641
614
  def __init__(self, env: EnvType | None = None) -> None:
642
- """
643
- Initialises an application with an
615
+ """Initialises an application with an
644
616
  :class:`~eventsourcing.persistence.InfrastructureFactory`,
645
617
  a :class:`~eventsourcing.persistence.Mapper`,
646
618
  an :class:`~eventsourcing.persistence.ApplicationRecorder`,
@@ -659,21 +631,15 @@ class Application:
659
631
  self._repository = self.construct_repository()
660
632
  self._notification_log = self.construct_notification_log()
661
633
  self.closing = Event()
662
- self.previous_max_notification_id: int | None = (
663
- self.recorder.max_notification_id()
664
- )
665
634
 
666
635
  @property
667
636
  def repository(self) -> Repository:
668
- """
669
- An application's repository reconstructs aggregates from stored events.
670
- """
637
+ """An application's repository reconstructs aggregates from stored events."""
671
638
  return self._repository
672
639
 
673
640
  @property
674
641
  def notification_log(self) -> LocalNotificationLog:
675
- """
676
- An application's notification log presents all the aggregate events
642
+ """An application's notification log presents all the aggregate events
677
643
  of an application in the order they were recorded as a sequence of event
678
644
  notifications.
679
645
  """
@@ -689,9 +655,7 @@ class Application:
689
655
  return self._notification_log
690
656
 
691
657
  def construct_env(self, name: str, env: EnvType | None = None) -> Environment:
692
- """
693
- Constructs environment from which application will be configured.
694
- """
658
+ """Constructs environment from which application will be configured."""
695
659
  _env = dict(type(self).env)
696
660
  if type(self).is_snapshotting_enabled or type(self).snapshotting_intervals:
697
661
  _env["IS_SNAPSHOTTING_ENABLED"] = "y"
@@ -701,31 +665,28 @@ class Application:
701
665
  return Environment(name, _env)
702
666
 
703
667
  def construct_factory(self, env: Environment) -> InfrastructureFactory:
704
- """
705
- Constructs an :class:`~eventsourcing.persistence.InfrastructureFactory`
668
+ """Constructs an :class:`~eventsourcing.persistence.InfrastructureFactory`
706
669
  for use by the application.
707
670
  """
708
671
  return InfrastructureFactory.construct(env)
709
672
 
710
673
  def construct_mapper(self) -> Mapper:
711
- """
712
- Constructs a :class:`~eventsourcing.persistence.Mapper`
674
+ """Constructs a :class:`~eventsourcing.persistence.Mapper`
713
675
  for use by the application.
714
676
  """
715
677
  return self.factory.mapper(transcoder=self.construct_transcoder())
716
678
 
717
679
  def construct_transcoder(self) -> Transcoder:
718
- """
719
- Constructs a :class:`~eventsourcing.persistence.Transcoder`
680
+ """Constructs a :class:`~eventsourcing.persistence.Transcoder`
720
681
  for use by the application.
721
682
  """
722
683
  transcoder = self.factory.transcoder()
723
- self.register_transcodings(transcoder)
684
+ if isinstance(transcoder, JSONTranscoder):
685
+ self.register_transcodings(transcoder)
724
686
  return transcoder
725
687
 
726
- def register_transcodings(self, transcoder: Transcoder) -> None:
727
- """
728
- Registers :class:`~eventsourcing.persistence.Transcoding`
688
+ def register_transcodings(self, transcoder: JSONTranscoder) -> None:
689
+ """Registers :class:`~eventsourcing.persistence.Transcoding`
729
690
  objects on given :class:`~eventsourcing.persistence.JSONTranscoder`.
730
691
  """
731
692
  transcoder.register(UUIDAsHex())
@@ -733,15 +694,13 @@ class Application:
733
694
  transcoder.register(DatetimeAsISO())
734
695
 
735
696
  def construct_recorder(self) -> ApplicationRecorder:
736
- """
737
- Constructs an :class:`~eventsourcing.persistence.ApplicationRecorder`
697
+ """Constructs an :class:`~eventsourcing.persistence.ApplicationRecorder`
738
698
  for use by the application.
739
699
  """
740
700
  return self.factory.application_recorder()
741
701
 
742
702
  def construct_event_store(self) -> EventStore:
743
- """
744
- Constructs an :class:`~eventsourcing.persistence.EventStore`
703
+ """Constructs an :class:`~eventsourcing.persistence.EventStore`
745
704
  for use by the application to store and retrieve aggregate
746
705
  :class:`~eventsourcing.domain.AggregateEvent` objects.
747
706
  """
@@ -751,8 +710,7 @@ class Application:
751
710
  )
752
711
 
753
712
  def construct_snapshot_store(self) -> EventStore:
754
- """
755
- Constructs an :class:`~eventsourcing.persistence.EventStore`
713
+ """Constructs an :class:`~eventsourcing.persistence.EventStore`
756
714
  for use by the application to store and retrieve aggregate
757
715
  :class:`~eventsourcing.domain.Snapshot` objects.
758
716
  """
@@ -763,9 +721,7 @@ class Application:
763
721
  )
764
722
 
765
723
  def construct_repository(self) -> Repository:
766
- """
767
- Constructs a :class:`Repository` for use by the application.
768
- """
724
+ """Constructs a :class:`Repository` for use by the application."""
769
725
  cache_maxsize_envvar = self.env.get(self.AGGREGATE_CACHE_MAXSIZE)
770
726
  cache_maxsize = int(cache_maxsize_envvar) if cache_maxsize_envvar else None
771
727
  return Repository(
@@ -782,18 +738,15 @@ class Application:
782
738
  )
783
739
 
784
740
  def construct_notification_log(self) -> LocalNotificationLog:
785
- """
786
- Constructs a :class:`LocalNotificationLog` for use by the application.
787
- """
741
+ """Constructs a :class:`LocalNotificationLog` for use by the application."""
788
742
  return LocalNotificationLog(self.recorder, section_size=self.log_section_size)
789
743
 
790
744
  def save(
791
745
  self,
792
746
  *objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
793
747
  **kwargs: Any,
794
- ) -> List[Recording]:
795
- """
796
- Collects pending events from given aggregates and
748
+ ) -> list[Recording]:
749
+ """Collects pending events from given aggregates and
797
750
  puts them in the application's event store.
798
751
  """
799
752
  processing_event = ProcessingEvent()
@@ -804,10 +757,8 @@ class Application:
804
757
  self.notify(processing_event.events) # Deprecated.
805
758
  return recordings
806
759
 
807
- def _record(self, processing_event: ProcessingEvent) -> List[Recording]:
808
- """
809
- Records given process event in the application's recorder.
810
- """
760
+ def _record(self, processing_event: ProcessingEvent) -> list[Recording]:
761
+ """Records given process event in the application's recorder."""
811
762
  recordings = self.events.put(
812
763
  processing_event.events,
813
764
  tracking=processing_event.tracking,
@@ -828,12 +779,9 @@ class Application:
828
779
  continue
829
780
  interval = self.snapshotting_intervals.get(type(aggregate))
830
781
  if interval is not None and event.originator_version % interval == 0:
831
- if (
832
- self.snapshotting_projectors
833
- and type(aggregate) in self.snapshotting_projectors
834
- ):
782
+ try:
835
783
  projector_func = self.snapshotting_projectors[type(aggregate)]
836
- else:
784
+ except KeyError:
837
785
  projector_func = project_aggregate
838
786
  if projector_func is project_aggregate and not isinstance(
839
787
  event, CanMutateProtocol
@@ -864,8 +812,7 @@ class Application:
864
812
  TMutableOrImmutableAggregate, TDomainEvent
865
813
  ] = project_aggregate,
866
814
  ) -> None:
867
- """
868
- Takes a snapshot of the recorded state of the aggregate,
815
+ """Takes a snapshot of the recorded state of the aggregate,
869
816
  and puts the snapshot in the snapshot store.
870
817
  """
871
818
  if self.snapshots is None:
@@ -884,9 +831,8 @@ class Application:
884
831
  snapshot = snapshot_class.take(aggregate)
885
832
  self.snapshots.put([snapshot])
886
833
 
887
- def notify(self, new_events: List[DomainEventProtocol]) -> None:
888
- """
889
- Deprecated.
834
+ def notify(self, new_events: list[DomainEventProtocol]) -> None:
835
+ """Deprecated.
890
836
 
891
837
  Called after new aggregate events have been saved. This
892
838
  method on this class doesn't actually do anything,
@@ -894,9 +840,8 @@ class Application:
894
840
  need to take action when new domain events have been saved.
895
841
  """
896
842
 
897
- def _notify(self, recordings: List[Recording]) -> None:
898
- """
899
- Called after new aggregate events have been saved. This
843
+ def _notify(self, recordings: list[Recording]) -> None:
844
+ """Called after new aggregate events have been saved. This
900
845
  method on this class doesn't actually do anything,
901
846
  but this method may be implemented by subclasses that
902
847
  need to take action when new domain events have been saved.
@@ -910,23 +855,14 @@ class Application:
910
855
  TApplication = TypeVar("TApplication", bound=Application)
911
856
 
912
857
 
913
- @deprecated(
914
- "AggregateNotFound is deprecated, use AggregateNotFoundError instead", category=None
915
- )
916
- class AggregateNotFound(EventSourcingError): # noqa: N818
917
- pass
918
-
919
-
920
- class AggregateNotFoundError(AggregateNotFound):
921
- """
922
- Raised when an :class:`~eventsourcing.domain.Aggregate`
858
+ class AggregateNotFoundError(EventSourcingError):
859
+ """Raised when an :class:`~eventsourcing.domain.Aggregate`
923
860
  object is not found in a :class:`Repository`.
924
861
  """
925
862
 
926
863
 
927
864
  class EventSourcedLog(Generic[TDomainEvent]):
928
- """
929
- Constructs a sequence of domain events, like an aggregate.
865
+ """Constructs a sequence of domain events, like an aggregate.
930
866
  But unlike an aggregate the events can be triggered
931
867
  and selected for use in an application without
932
868
  reconstructing a current state from all the events.
@@ -943,7 +879,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
943
879
  self,
944
880
  events: EventStore,
945
881
  originator_id: UUID,
946
- logged_cls: Type[TDomainEvent], # TODO: Rename to 'event_class' in v10.
882
+ logged_cls: type[TDomainEvent], # TODO: Rename to 'event_class' in v10.
947
883
  ):
948
884
  self.events = events
949
885
  self.originator_id = originator_id
@@ -954,9 +890,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
954
890
  next_originator_version: int | None = None,
955
891
  **kwargs: Any,
956
892
  ) -> TDomainEvent:
957
- """
958
- Constructs and returns a new log event.
959
- """
893
+ """Constructs and returns a new log event."""
960
894
  return self._trigger_event(
961
895
  logged_cls=self.logged_cls,
962
896
  next_originator_version=next_originator_version,
@@ -965,13 +899,11 @@ class EventSourcedLog(Generic[TDomainEvent]):
965
899
 
966
900
  def _trigger_event(
967
901
  self,
968
- logged_cls: Type[T] | None,
902
+ logged_cls: type[SDomainEvent],
969
903
  next_originator_version: int | None = None,
970
904
  **kwargs: Any,
971
- ) -> T:
972
- """
973
- Constructs and returns a new log event.
974
- """
905
+ ) -> SDomainEvent:
906
+ """Constructs and returns a new log event."""
975
907
  if next_originator_version is None:
976
908
  last_logged = self.get_last()
977
909
  if last_logged is None:
@@ -979,26 +911,22 @@ class EventSourcedLog(Generic[TDomainEvent]):
979
911
  else:
980
912
  next_originator_version = last_logged.originator_version + 1
981
913
 
982
- return logged_cls( # type: ignore
914
+ return logged_cls(
983
915
  originator_id=self.originator_id,
984
916
  originator_version=next_originator_version,
985
- timestamp=create_utc_datetime_now(),
917
+ timestamp=datetime_now_with_tzinfo(),
986
918
  **kwargs,
987
919
  )
988
920
 
989
921
  def get_first(self) -> TDomainEvent | None:
990
- """
991
- Selects the first logged event.
992
- """
922
+ """Selects the first logged event."""
993
923
  try:
994
924
  return next(self.get(limit=1))
995
925
  except StopIteration:
996
926
  return None
997
927
 
998
928
  def get_last(self) -> TDomainEvent | None:
999
- """
1000
- Selects the last logged event.
1001
- """
929
+ """Selects the last logged event."""
1002
930
  try:
1003
931
  return next(self.get(desc=True, limit=1))
1004
932
  except StopIteration:
@@ -1012,12 +940,11 @@ class EventSourcedLog(Generic[TDomainEvent]):
1012
940
  desc: bool = False,
1013
941
  limit: int | None = None,
1014
942
  ) -> Iterator[TDomainEvent]:
1015
- """
1016
- Selects a range of logged events with limit,
943
+ """Selects a range of logged events with limit,
1017
944
  with ascending or descending order.
1018
945
  """
1019
946
  return cast(
1020
- Iterator[TDomainEvent],
947
+ "Iterator[TDomainEvent]",
1021
948
  self.events.get(
1022
949
  originator_id=self.originator_id,
1023
950
  gt=gt,