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/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "9.2.22"
1
+ __version__ = "9.3.0dev0"
@@ -7,8 +7,10 @@ from dataclasses import dataclass
7
7
  from itertools import chain
8
8
  from threading import Event, Lock
9
9
  from typing import (
10
+ TYPE_CHECKING,
10
11
  Any,
11
12
  Callable,
13
+ ClassVar,
12
14
  Dict,
13
15
  Generic,
14
16
  Iterable,
@@ -19,16 +21,10 @@ from typing import (
19
21
  Tuple,
20
22
  Type,
21
23
  TypeVar,
22
- Union,
23
24
  cast,
24
25
  )
25
- from uuid import UUID
26
26
  from warnings import warn
27
27
 
28
- # For backwards compatibility of import statements...
29
- from eventsourcing.domain import LogEvent # noqa: F401
30
- from eventsourcing.domain import TLogEvent # noqa: F401
31
- from eventsourcing.domain import create_utc_datetime_now # noqa: F401
32
28
  from eventsourcing.domain import (
33
29
  Aggregate,
34
30
  CanMutateProtocol,
@@ -41,6 +37,7 @@ from eventsourcing.domain import (
41
37
  SnapshotProtocol,
42
38
  TDomainEvent,
43
39
  TMutableOrImmutableAggregate,
40
+ create_utc_datetime_now,
44
41
  )
45
42
  from eventsourcing.persistence import (
46
43
  ApplicationRecorder,
@@ -57,6 +54,9 @@ from eventsourcing.persistence import (
57
54
  )
58
55
  from eventsourcing.utils import Environment, EnvType, strtobool
59
56
 
57
+ if TYPE_CHECKING: # pragma: nocover
58
+ from uuid import UUID
59
+
60
60
  ProjectorFunction = Callable[
61
61
  [Optional[TMutableOrImmutableAggregate], Iterable[TDomainEvent]],
62
62
  Optional[TMutableOrImmutableAggregate],
@@ -69,9 +69,9 @@ MutatorFunction = Callable[
69
69
 
70
70
 
71
71
  def project_aggregate(
72
- aggregate: Optional[TMutableOrImmutableAggregate],
72
+ aggregate: TMutableOrImmutableAggregate | None,
73
73
  domain_events: Iterable[DomainEventProtocol],
74
- ) -> Optional[TMutableOrImmutableAggregate]:
74
+ ) -> TMutableOrImmutableAggregate | None:
75
75
  """
76
76
  Projector function for aggregate projections, which works
77
77
  by successively calling aggregate mutator function mutate()
@@ -91,13 +91,12 @@ class Cache(Generic[S, T]):
91
91
  def __init__(self) -> None:
92
92
  self.cache: Dict[S, Any] = {}
93
93
 
94
- def get(self, key: S, evict: bool = False) -> T:
94
+ def get(self, key: S, *, evict: bool = False) -> T:
95
95
  if evict:
96
96
  return self.cache.pop(key)
97
- else:
98
- return self.cache[key]
97
+ return self.cache[key]
99
98
 
100
- def put(self, key: S, value: T) -> Optional[T]:
99
+ def put(self, key: S, value: T) -> T | None:
101
100
  if value is not None:
102
101
  self.cache[key] = value
103
102
  return None
@@ -132,7 +131,7 @@ class LRUCache(Cache[S, T]):
132
131
  None,
133
132
  ] # initialize by pointing to self
134
133
 
135
- def get(self, key: S, evict: bool = False) -> T:
134
+ def get(self, key: S, *, evict: bool = False) -> T:
136
135
  with self.lock:
137
136
  link = self.cache.get(key)
138
137
  if link is not None:
@@ -153,10 +152,9 @@ class LRUCache(Cache[S, T]):
153
152
  self.full = self.cache.__len__() >= self.maxsize
154
153
 
155
154
  return result
156
- else:
157
- raise KeyError
155
+ raise KeyError
158
156
 
159
- def put(self, key: S, value: T) -> Optional[Any]:
157
+ def put(self, key: S, value: T) -> Any | None:
160
158
  evicted_key = None
161
159
  evicted_value = None
162
160
  with self.lock:
@@ -215,8 +213,9 @@ class Repository:
215
213
  def __init__(
216
214
  self,
217
215
  event_store: EventStore,
218
- snapshot_store: Optional[EventStore] = None,
219
- cache_maxsize: Optional[int] = None,
216
+ *,
217
+ snapshot_store: EventStore | None = None,
218
+ cache_maxsize: int | None = None,
220
219
  fastforward: bool = True,
221
220
  fastforward_skipping: bool = False,
222
221
  deepcopy_from_cache: bool = True,
@@ -233,7 +232,7 @@ class Repository:
233
232
  self.snapshot_store = snapshot_store
234
233
 
235
234
  if cache_maxsize is None:
236
- self.cache: Optional[Cache[UUID, MutableOrImmutableAggregate]] = None
235
+ self.cache: Cache[UUID, MutableOrImmutableAggregate] | None = None
237
236
  elif cache_maxsize <= 0:
238
237
  self.cache = Cache()
239
238
  else:
@@ -252,7 +251,8 @@ class Repository:
252
251
  def get(
253
252
  self,
254
253
  aggregate_id: UUID,
255
- version: Optional[int] = None,
254
+ *,
255
+ version: int | None = None,
256
256
  projector_func: ProjectorFunction[
257
257
  TMutableOrImmutableAggregate, TDomainEvent
258
258
  ] = project_aggregate,
@@ -291,9 +291,8 @@ class Repository:
291
291
  aggregate, cast(Iterable[TDomainEvent], new_events)
292
292
  )
293
293
  if _aggregate is None:
294
- raise AggregateNotFound(aggregate_id)
295
- else:
296
- aggregate = _aggregate
294
+ raise AggregateNotFoundError(aggregate_id)
295
+ aggregate = _aggregate
297
296
  finally:
298
297
  fastforward_lock.release()
299
298
  finally:
@@ -312,10 +311,10 @@ class Repository:
312
311
  def _reconstruct_aggregate(
313
312
  self,
314
313
  aggregate_id: UUID,
315
- version: Optional[int],
314
+ version: int | None,
316
315
  projector_func: ProjectorFunction[TMutableOrImmutableAggregate, TDomainEvent],
317
316
  ) -> TMutableOrImmutableAggregate:
318
- gt: Optional[int] = None
317
+ gt: int | None = None
319
318
 
320
319
  if self.snapshot_store is not None:
321
320
  # Try to get a snapshot.
@@ -340,7 +339,7 @@ class Repository:
340
339
  )
341
340
 
342
341
  # Reconstruct the aggregate from its events.
343
- initial: Optional[TMutableOrImmutableAggregate] = None
342
+ initial: TMutableOrImmutableAggregate | None = None
344
343
  aggregate = projector_func(
345
344
  initial,
346
345
  chain(
@@ -351,10 +350,9 @@ class Repository:
351
350
 
352
351
  # Raise exception if "not found".
353
352
  if aggregate is None:
354
- raise AggregateNotFound((aggregate_id, version))
355
- else:
356
- # Return the aggregate.
357
- return aggregate
353
+ raise AggregateNotFoundError((aggregate_id, version))
354
+ # Return the aggregate.
355
+ return aggregate
358
356
 
359
357
  def _use_fastforward_lock(self, aggregate_id: UUID) -> Lock:
360
358
  with self._fastforward_locks_lock:
@@ -388,7 +386,7 @@ class Repository:
388
386
  """
389
387
  try:
390
388
  self.get(aggregate_id=item)
391
- except AggregateNotFound:
389
+ except AggregateNotFoundError:
392
390
  return False
393
391
  else:
394
392
  return True
@@ -414,9 +412,9 @@ class Section:
414
412
  :param Optional[str] next_id: section ID of the following section
415
413
  """
416
414
 
417
- id: Optional[str]
415
+ id: str | None
418
416
  items: List[Notification]
419
- next_id: Optional[str]
417
+ next_id: str | None
420
418
 
421
419
 
422
420
  class NotificationLog(ABC):
@@ -437,7 +435,7 @@ class NotificationLog(ABC):
437
435
  self,
438
436
  start: int,
439
437
  limit: int,
440
- stop: Optional[int] = None,
438
+ stop: int | None = None,
441
439
  topics: Sequence[str] = (),
442
440
  ) -> List[Notification]:
443
441
  """
@@ -497,8 +495,8 @@ class LocalNotificationLog(NotificationLog):
497
495
  notifications = self.select(start, limit)
498
496
 
499
497
  # Get next section ID.
500
- actual_section_id: Optional[str]
501
- next_id: Optional[str]
498
+ actual_section_id: str | None
499
+ next_id: str | None
502
500
  if len(notifications):
503
501
  last_notification_id = notifications[-1].id
504
502
  actual_section_id = self.format_section_id(
@@ -525,7 +523,7 @@ class LocalNotificationLog(NotificationLog):
525
523
  self,
526
524
  start: int,
527
525
  limit: int,
528
- stop: Optional[int] = None,
526
+ stop: int | None = None,
529
527
  topics: Sequence[str] = (),
530
528
  ) -> List[Notification]:
531
529
  """
@@ -534,16 +532,17 @@ class LocalNotificationLog(NotificationLog):
534
532
  from the notification log.
535
533
  """
536
534
  if limit > self.section_size:
537
- raise ValueError(
535
+ msg = (
538
536
  f"Requested limit {limit} greater than section size {self.section_size}"
539
537
  )
538
+ raise ValueError(msg)
540
539
  return self.recorder.select_notifications(
541
540
  start=start, limit=limit, stop=stop, topics=topics
542
541
  )
543
542
 
544
543
  @staticmethod
545
544
  def format_section_id(first_id: int, last_id: int) -> str:
546
- return "{},{}".format(first_id, last_id)
545
+ return f"{first_id},{last_id}"
547
546
 
548
547
 
549
548
  class ProcessingEvent:
@@ -554,7 +553,7 @@ class ProcessingEvent:
554
553
  new domain events that result from processing that notification.
555
554
  """
556
555
 
557
- def __init__(self, tracking: Optional[Tracking] = None):
556
+ def __init__(self, tracking: Tracking | None = None):
558
557
  """
559
558
  Initialises the process event with the given tracking object.
560
559
  """
@@ -565,7 +564,7 @@ class ProcessingEvent:
565
564
 
566
565
  def collect_events(
567
566
  self,
568
- *objs: Optional[Union[MutableOrImmutableAggregate, DomainEventProtocol]],
567
+ *objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
569
568
  **kwargs: Any,
570
569
  ) -> None:
571
570
  """
@@ -574,7 +573,7 @@ class ProcessingEvent:
574
573
  for obj in objs:
575
574
  if obj is None:
576
575
  continue
577
- elif isinstance(obj, DomainEventProtocol):
576
+ if isinstance(obj, DomainEventProtocol):
578
577
  self.events.append(obj)
579
578
  else:
580
579
  if isinstance(obj, CollectEventsProtocol):
@@ -586,7 +585,7 @@ class ProcessingEvent:
586
585
 
587
586
  def save(
588
587
  self,
589
- *aggregates: Optional[Union[MutableOrImmutableAggregate, DomainEventProtocol]],
588
+ *aggregates: MutableOrImmutableAggregate | DomainEventProtocol | None,
590
589
  **kwargs: Any,
591
590
  ) -> None:
592
591
  warn(
@@ -598,31 +597,12 @@ class ProcessingEvent:
598
597
  self.collect_events(*aggregates, **kwargs)
599
598
 
600
599
 
601
- class ProcessEvent(ProcessingEvent):
602
- """Deprecated, use :class:`ProcessingEvent` instead.
603
-
604
- Keeps together a :class:`~eventsourcing.persistence.Tracking`
605
- object, which represents the position of a domain event notification
606
- in the notification log of a particular application, and the
607
- new domain events that result from processing that notification.
608
- """
609
-
610
- def __init__(self, tracking: Optional[Tracking] = None):
611
- warn(
612
- "'ProcessEvent' is deprecated, use 'ProcessingEvent' instead",
613
- DeprecationWarning,
614
- stacklevel=2,
615
- )
616
-
617
- super().__init__(tracking)
618
-
619
-
620
600
  class RecordingEvent:
621
601
  def __init__(
622
602
  self,
623
603
  application_name: str,
624
604
  recordings: List[Recording],
625
- previous_max_notification_id: Optional[int],
605
+ previous_max_notification_id: int | None,
626
606
  ):
627
607
  self.application_name = application_name
628
608
  self.recordings = recordings
@@ -635,13 +615,13 @@ class Application:
635
615
  """
636
616
 
637
617
  name = "Application"
638
- env: EnvType = {}
618
+ env: ClassVar[Dict[str, str]] = {}
639
619
  is_snapshotting_enabled: bool = False
640
- snapshotting_intervals: Optional[
641
- Dict[Type[MutableOrImmutableAggregate], int]
620
+ snapshotting_intervals: ClassVar[
621
+ Dict[Type[MutableOrImmutableAggregate], int] | None
642
622
  ] = None
643
- snapshotting_projectors: Optional[
644
- Dict[Type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]]
623
+ snapshotting_projectors: ClassVar[
624
+ Dict[Type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]] | None
645
625
  ] = None
646
626
  snapshot_class: Type[SnapshotProtocol] = Snapshot
647
627
  log_section_size = 10
@@ -656,7 +636,7 @@ class Application:
656
636
  if "name" not in cls.__dict__:
657
637
  cls.name = cls.__name__
658
638
 
659
- def __init__(self, env: Optional[EnvType] = None) -> None:
639
+ def __init__(self, env: EnvType | None = None) -> None:
660
640
  """
661
641
  Initialises an application with an
662
642
  :class:`~eventsourcing.persistence.InfrastructureFactory`,
@@ -666,20 +646,20 @@ class Application:
666
646
  a :class:`~eventsourcing.application.Repository`, and
667
647
  a :class:`~eventsourcing.application.LocalNotificationLog`.
668
648
  """
669
- self.env = self.construct_env(self.name, env)
649
+ self.env = self.construct_env(self.name, env) # type: ignore[misc]
670
650
  self.factory = self.construct_factory(self.env)
671
651
  self.mapper = self.construct_mapper()
672
652
  self.recorder = self.construct_recorder()
673
653
  self.events = self.construct_event_store()
674
- self.snapshots: Optional[EventStore] = None
654
+ self.snapshots: EventStore | None = None
675
655
  if self.factory.is_snapshotting_enabled():
676
656
  self.snapshots = self.construct_snapshot_store()
677
657
  self._repository = self.construct_repository()
678
658
  self._notification_log = self.construct_notification_log()
679
659
  self.closing = Event()
680
- self.previous_max_notification_id: Optional[
681
- int
682
- ] = self.recorder.max_notification_id()
660
+ self.previous_max_notification_id: int | None = (
661
+ self.recorder.max_notification_id()
662
+ )
683
663
 
684
664
  @property
685
665
  def repository(self) -> Repository:
@@ -706,7 +686,7 @@ class Application:
706
686
  )
707
687
  return self._notification_log
708
688
 
709
- def construct_env(self, name: str, env: Optional[EnvType] = None) -> Environment:
689
+ def construct_env(self, name: str, env: EnvType | None = None) -> Environment:
710
690
  """
711
691
  Constructs environment from which application will be configured.
712
692
  """
@@ -785,10 +765,7 @@ class Application:
785
765
  Constructs a :class:`Repository` for use by the application.
786
766
  """
787
767
  cache_maxsize_envvar = self.env.get(self.AGGREGATE_CACHE_MAXSIZE)
788
- if cache_maxsize_envvar:
789
- cache_maxsize = int(cache_maxsize_envvar)
790
- else:
791
- cache_maxsize = None
768
+ cache_maxsize = int(cache_maxsize_envvar) if cache_maxsize_envvar else None
792
769
  return Repository(
793
770
  event_store=self.events,
794
771
  snapshot_store=self.snapshots,
@@ -810,7 +787,7 @@ class Application:
810
787
 
811
788
  def save(
812
789
  self,
813
- *objs: Optional[Union[MutableOrImmutableAggregate, DomainEventProtocol]],
790
+ *objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
814
791
  **kwargs: Any,
815
792
  ) -> List[Recording]:
816
793
  """
@@ -848,43 +825,39 @@ class Application:
848
825
  except KeyError:
849
826
  continue
850
827
  interval = self.snapshotting_intervals.get(type(aggregate))
851
- if interval is not None:
852
- if event.originator_version % interval == 0:
853
- if (
854
- self.snapshotting_projectors
855
- and type(aggregate) in self.snapshotting_projectors
856
- ):
857
- projector_func = self.snapshotting_projectors[
858
- type(aggregate)
859
- ]
860
- else:
861
- projector_func = project_aggregate
862
- if projector_func is project_aggregate and not isinstance(
863
- event, CanMutateProtocol
864
- ):
865
- raise ProgrammingError(
866
- (
867
- f"Cannot take snapshot for {type(aggregate)} with "
868
- "default project_aggregate() function, because its "
869
- f"domain event {type(event)} does not implement "
870
- "the 'can mutate' protocol (see CanMutateProtocol)."
871
- f" Please define application class {type(self)}"
872
- " with class variable 'snapshotting_projectors', "
873
- f"to be a dict that has {type(aggregate)} as a key "
874
- "with the aggregate projector function for "
875
- f"{type(aggregate)} as the value for that key."
876
- )
877
- )
878
- self.take_snapshot(
879
- aggregate_id=event.originator_id,
880
- version=event.originator_version,
881
- projector_func=projector_func,
828
+ if interval is not None and event.originator_version % interval == 0:
829
+ if (
830
+ self.snapshotting_projectors
831
+ and type(aggregate) in self.snapshotting_projectors
832
+ ):
833
+ projector_func = self.snapshotting_projectors[type(aggregate)]
834
+ else:
835
+ projector_func = project_aggregate
836
+ if projector_func is project_aggregate and not isinstance(
837
+ event, CanMutateProtocol
838
+ ):
839
+ msg = (
840
+ f"Cannot take snapshot for {type(aggregate)} with "
841
+ "default project_aggregate() function, because its "
842
+ f"domain event {type(event)} does not implement "
843
+ "the 'can mutate' protocol (see CanMutateProtocol)."
844
+ f" Please define application class {type(self)}"
845
+ " with class variable 'snapshotting_projectors', "
846
+ f"to be a dict that has {type(aggregate)} as a key "
847
+ "with the aggregate projector function for "
848
+ f"{type(aggregate)} as the value for that key."
882
849
  )
850
+ raise ProgrammingError(msg)
851
+ self.take_snapshot(
852
+ aggregate_id=event.originator_id,
853
+ version=event.originator_version,
854
+ projector_func=projector_func,
855
+ )
883
856
 
884
857
  def take_snapshot(
885
858
  self,
886
859
  aggregate_id: UUID,
887
- version: Optional[int] = None,
860
+ version: int | None = None,
888
861
  projector_func: ProjectorFunction[
889
862
  TMutableOrImmutableAggregate, TDomainEvent
890
863
  ] = project_aggregate,
@@ -894,22 +867,20 @@ class Application:
894
867
  and puts the snapshot in the snapshot store.
895
868
  """
896
869
  if self.snapshots is None:
897
- raise AssertionError(
870
+ msg = (
898
871
  "Can't take snapshot without snapshots store. Please "
899
872
  "set environment variable IS_SNAPSHOTTING_ENABLED to "
900
873
  "a true value (e.g. 'y'), or set 'is_snapshotting_enabled' "
901
874
  "on application class, or set 'snapshotting_intervals' on "
902
875
  "application class."
903
876
  )
904
- else:
905
- aggregate = self.repository.get(
906
- aggregate_id, version=version, projector_func=projector_func
907
- )
908
- snapshot_class = getattr(
909
- type(aggregate), "Snapshot", type(self).snapshot_class
910
- )
911
- snapshot = snapshot_class.take(aggregate)
912
- self.snapshots.put([snapshot])
877
+ raise AssertionError(msg)
878
+ aggregate = self.repository.get(
879
+ aggregate_id, version=version, projector_func=projector_func
880
+ )
881
+ snapshot_class = getattr(type(aggregate), "Snapshot", type(self).snapshot_class)
882
+ snapshot = snapshot_class.take(aggregate)
883
+ self.snapshots.put([snapshot])
913
884
 
914
885
  def notify(self, new_events: List[DomainEventProtocol]) -> None:
915
886
  """
@@ -937,7 +908,7 @@ class Application:
937
908
  TApplication = TypeVar("TApplication", bound=Application)
938
909
 
939
910
 
940
- class AggregateNotFound(EventSourcingError):
911
+ class AggregateNotFoundError(EventSourcingError):
941
912
  """
942
913
  Raised when an :class:`~eventsourcing.domain.Aggregate`
943
914
  object is not found in a :class:`Repository`.
@@ -963,15 +934,15 @@ class EventSourcedLog(Generic[TDomainEvent]):
963
934
  self,
964
935
  events: EventStore,
965
936
  originator_id: UUID,
966
- logged_cls: Type[TDomainEvent], # Todo: Rename to 'event_class' in v10.
937
+ logged_cls: Type[TDomainEvent], # TODO: Rename to 'event_class' in v10.
967
938
  ):
968
939
  self.events = events
969
940
  self.originator_id = originator_id
970
- self.logged_cls = logged_cls # Todo: Rename to 'event_class' in v10.
941
+ self.logged_cls = logged_cls # TODO: Rename to 'event_class' in v10.
971
942
 
972
943
  def trigger_event(
973
944
  self,
974
- next_originator_version: Optional[int] = None,
945
+ next_originator_version: int | None = None,
975
946
  **kwargs: Any,
976
947
  ) -> TDomainEvent:
977
948
  """
@@ -985,8 +956,8 @@ class EventSourcedLog(Generic[TDomainEvent]):
985
956
 
986
957
  def _trigger_event(
987
958
  self,
988
- logged_cls: Optional[Type[T]],
989
- next_originator_version: Optional[int] = None,
959
+ logged_cls: Type[T] | None,
960
+ next_originator_version: int | None = None,
990
961
  **kwargs: Any,
991
962
  ) -> T:
992
963
  """
@@ -999,15 +970,14 @@ class EventSourcedLog(Generic[TDomainEvent]):
999
970
  else:
1000
971
  next_originator_version = last_logged.originator_version + 1
1001
972
 
1002
- logged_event = logged_cls( # type: ignore
973
+ return logged_cls( # type: ignore
1003
974
  originator_id=self.originator_id,
1004
975
  originator_version=next_originator_version,
1005
976
  timestamp=create_utc_datetime_now(),
1006
977
  **kwargs,
1007
978
  )
1008
- return logged_event
1009
979
 
1010
- def get_first(self) -> Optional[TDomainEvent]:
980
+ def get_first(self) -> TDomainEvent | None:
1011
981
  """
1012
982
  Selects the first logged event.
1013
983
  """
@@ -1016,7 +986,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
1016
986
  except StopIteration:
1017
987
  return None
1018
988
 
1019
- def get_last(self) -> Optional[TDomainEvent]:
989
+ def get_last(self) -> TDomainEvent | None:
1020
990
  """
1021
991
  Selects the last logged event.
1022
992
  """
@@ -1027,10 +997,11 @@ class EventSourcedLog(Generic[TDomainEvent]):
1027
997
 
1028
998
  def get(
1029
999
  self,
1030
- gt: Optional[int] = None,
1031
- lte: Optional[int] = None,
1000
+ *,
1001
+ gt: int | None = None,
1002
+ lte: int | None = None,
1032
1003
  desc: bool = False,
1033
- limit: Optional[int] = None,
1004
+ limit: int | None = None,
1034
1005
  ) -> Iterator[TDomainEvent]:
1035
1006
  """
1036
1007
  Selects a range of logged events with limit,
eventsourcing/cipher.py CHANGED
@@ -2,13 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  from base64 import b64decode, b64encode
5
+ from typing import TYPE_CHECKING
5
6
 
6
7
  from Crypto.Cipher import AES
7
8
  from Crypto.Cipher._mode_gcm import GcmMode
8
9
  from Crypto.Cipher.AES import key_size
9
10
 
10
11
  from eventsourcing.persistence import Cipher
11
- from eventsourcing.utils import Environment
12
+
13
+ if TYPE_CHECKING: # pragma: nocover
14
+ from eventsourcing.utils import Environment
12
15
 
13
16
 
14
17
  class AESCipher(Cipher):
@@ -33,9 +36,8 @@ class AESCipher(Cipher):
33
36
  @staticmethod
34
37
  def check_key_size(num_bytes: int) -> None:
35
38
  if num_bytes not in AESCipher.KEY_SIZES:
36
- raise ValueError(
37
- "Invalid key size: {} not in {}".format(num_bytes, AESCipher.KEY_SIZES)
38
- )
39
+ msg = f"Invalid key size: {num_bytes} not in {AESCipher.KEY_SIZES}"
40
+ raise ValueError(msg)
39
41
 
40
42
  @staticmethod
41
43
  def random_bytes(num_bytes: int) -> bytes:
@@ -49,7 +51,8 @@ class AESCipher(Cipher):
49
51
  """
50
52
  cipher_key = environment.get(self.CIPHER_KEY)
51
53
  if not cipher_key:
52
- raise EnvironmentError(f"'{self.CIPHER_KEY}' not set in env")
54
+ msg = f"'{self.CIPHER_KEY}' not in env"
55
+ raise OSError(msg)
53
56
  key = b64decode(cipher_key.encode("utf8"))
54
57
  AESCipher.check_key_size(len(key))
55
58
  self.key = key
@@ -66,11 +69,8 @@ class AESCipher(Cipher):
66
69
  encrypted = result[0]
67
70
  tag = result[1]
68
71
 
69
- # Combine with nonce.
70
- ciphertext = nonce + tag + encrypted
71
-
72
72
  # Return ciphertext.
73
- return ciphertext
73
+ return nonce + tag + encrypted
74
74
 
75
75
  def construct_cipher(self, nonce: bytes) -> GcmMode:
76
76
  cipher = AES.new(
@@ -87,11 +87,13 @@ class AESCipher(Cipher):
87
87
  # Split out the nonce, tag, and encrypted data.
88
88
  nonce = ciphertext[:12]
89
89
  if len(nonce) != 12:
90
- raise ValueError("Damaged cipher text: invalid nonce length")
90
+ msg = "Damaged cipher text: invalid nonce length"
91
+ raise ValueError(msg)
91
92
 
92
93
  tag = ciphertext[12:28]
93
94
  if len(tag) != 16:
94
- raise ValueError("Damaged cipher text: invalid tag length")
95
+ msg = "Damaged cipher text: invalid tag length"
96
+ raise ValueError(msg)
95
97
  encrypted = ciphertext[28:]
96
98
 
97
99
  # Construct AES cipher, with old nonce.
@@ -101,5 +103,6 @@ class AESCipher(Cipher):
101
103
  try:
102
104
  plaintext = cipher.decrypt_and_verify(encrypted, tag)
103
105
  except ValueError as e:
104
- raise ValueError("Cipher text is damaged: {}".format(e))
106
+ msg = f"Cipher text is damaged: {e}"
107
+ raise ValueError(msg) from None
105
108
  return plaintext