eventsourcing 9.4.4__tar.gz → 9.4.6__tar.gz
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-9.4.4 → eventsourcing-9.4.6}/PKG-INFO +18 -12
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/README.md +17 -11
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/application.py +92 -68
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/domain.py +479 -226
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/interface.py +10 -2
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/persistence.py +121 -33
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/popo.py +16 -14
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/postgres.py +20 -20
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/projection.py +25 -20
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/sqlite.py +28 -18
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/system.py +130 -90
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/tests/application.py +14 -97
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/tests/persistence.py +86 -18
- eventsourcing-9.4.6/eventsourcing/tests/postgres_utils.py +71 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/utils.py +1 -1
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/pyproject.toml +6 -3
- eventsourcing-9.4.4/eventsourcing/tests/postgres_utils.py +0 -51
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/AUTHORS +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/LICENSE +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/__init__.py +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/cipher.py +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/compressor.py +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/cryptography.py +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/dispatch.py +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/py.typed +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/tests/__init__.py +0 -0
- {eventsourcing-9.4.4 → eventsourcing-9.4.6}/eventsourcing/tests/domain.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: eventsourcing
|
|
3
|
-
Version: 9.4.
|
|
3
|
+
Version: 9.4.6
|
|
4
4
|
Summary: Event sourcing in Python
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
|
|
@@ -54,6 +54,8 @@ experience. Please [read the docs](https://eventsourcing.readthedocs.io/). See a
|
|
|
54
54
|
|
|
55
55
|
*"a huge help and time saver"*
|
|
56
56
|
|
|
57
|
+
[](https://deepwiki.com/pyeventsourcing/eventsourcing)
|
|
58
|
+
|
|
57
59
|
|
|
58
60
|
## Installation
|
|
59
61
|
|
|
@@ -75,40 +77,44 @@ from eventsourcing.domain import Aggregate, event
|
|
|
75
77
|
|
|
76
78
|
class Dog(Aggregate):
|
|
77
79
|
@event('Registered')
|
|
78
|
-
def __init__(self, name):
|
|
80
|
+
def __init__(self, name: str) -> None:
|
|
79
81
|
self.name = name
|
|
80
|
-
self.tricks = []
|
|
82
|
+
self.tricks: list[str] = []
|
|
81
83
|
|
|
82
84
|
@event('TrickAdded')
|
|
83
|
-
def add_trick(self, trick):
|
|
85
|
+
def add_trick(self, trick: str) -> None:
|
|
84
86
|
self.tricks.append(trick)
|
|
85
87
|
```
|
|
86
88
|
|
|
87
89
|
Define application objects with the `Application` class.
|
|
88
90
|
|
|
89
91
|
```python
|
|
92
|
+
from typing import Any
|
|
93
|
+
from uuid import UUID
|
|
94
|
+
|
|
90
95
|
from eventsourcing.application import Application
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
|
|
98
|
+
class DogSchool(Application[UUID]):
|
|
99
|
+
def register_dog(self, name: str) -> UUID:
|
|
94
100
|
dog = Dog(name)
|
|
95
101
|
self.save(dog)
|
|
96
102
|
return dog.id
|
|
97
103
|
|
|
98
|
-
def add_trick(self, dog_id, trick):
|
|
99
|
-
dog = self.repository.get(dog_id)
|
|
104
|
+
def add_trick(self, dog_id: UUID, trick: str) -> None:
|
|
105
|
+
dog: Dog = self.repository.get(dog_id)
|
|
100
106
|
dog.add_trick(trick)
|
|
101
107
|
self.save(dog)
|
|
102
108
|
|
|
103
|
-
def get_dog(self, dog_id):
|
|
104
|
-
dog = self.repository.get(dog_id)
|
|
109
|
+
def get_dog(self, dog_id: UUID) -> dict[str, Any]:
|
|
110
|
+
dog: Dog = self.repository.get(dog_id)
|
|
105
111
|
return {'name': dog.name, 'tricks': tuple(dog.tricks)}
|
|
106
112
|
```
|
|
107
113
|
|
|
108
114
|
Write a test.
|
|
109
115
|
|
|
110
116
|
```python
|
|
111
|
-
def test_dog_school():
|
|
117
|
+
def test_dog_school() -> None:
|
|
112
118
|
# Construct application object.
|
|
113
119
|
school = DogSchool()
|
|
114
120
|
|
|
@@ -140,7 +146,7 @@ Configure the application to run with an SQLite database. Other persistence modu
|
|
|
140
146
|
import os
|
|
141
147
|
|
|
142
148
|
os.environ["PERSISTENCE_MODULE"] = 'eventsourcing.sqlite'
|
|
143
|
-
os.environ["SQLITE_DBNAME"] = '
|
|
149
|
+
os.environ["SQLITE_DBNAME"] = ':memory:'
|
|
144
150
|
```
|
|
145
151
|
|
|
146
152
|
Run the test with SQLite.
|
|
@@ -19,6 +19,8 @@ experience. Please [read the docs](https://eventsourcing.readthedocs.io/). See a
|
|
|
19
19
|
|
|
20
20
|
*"a huge help and time saver"*
|
|
21
21
|
|
|
22
|
+
[](https://deepwiki.com/pyeventsourcing/eventsourcing)
|
|
23
|
+
|
|
22
24
|
|
|
23
25
|
## Installation
|
|
24
26
|
|
|
@@ -40,40 +42,44 @@ from eventsourcing.domain import Aggregate, event
|
|
|
40
42
|
|
|
41
43
|
class Dog(Aggregate):
|
|
42
44
|
@event('Registered')
|
|
43
|
-
def __init__(self, name):
|
|
45
|
+
def __init__(self, name: str) -> None:
|
|
44
46
|
self.name = name
|
|
45
|
-
self.tricks = []
|
|
47
|
+
self.tricks: list[str] = []
|
|
46
48
|
|
|
47
49
|
@event('TrickAdded')
|
|
48
|
-
def add_trick(self, trick):
|
|
50
|
+
def add_trick(self, trick: str) -> None:
|
|
49
51
|
self.tricks.append(trick)
|
|
50
52
|
```
|
|
51
53
|
|
|
52
54
|
Define application objects with the `Application` class.
|
|
53
55
|
|
|
54
56
|
```python
|
|
57
|
+
from typing import Any
|
|
58
|
+
from uuid import UUID
|
|
59
|
+
|
|
55
60
|
from eventsourcing.application import Application
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
|
|
63
|
+
class DogSchool(Application[UUID]):
|
|
64
|
+
def register_dog(self, name: str) -> UUID:
|
|
59
65
|
dog = Dog(name)
|
|
60
66
|
self.save(dog)
|
|
61
67
|
return dog.id
|
|
62
68
|
|
|
63
|
-
def add_trick(self, dog_id, trick):
|
|
64
|
-
dog = self.repository.get(dog_id)
|
|
69
|
+
def add_trick(self, dog_id: UUID, trick: str) -> None:
|
|
70
|
+
dog: Dog = self.repository.get(dog_id)
|
|
65
71
|
dog.add_trick(trick)
|
|
66
72
|
self.save(dog)
|
|
67
73
|
|
|
68
|
-
def get_dog(self, dog_id):
|
|
69
|
-
dog = self.repository.get(dog_id)
|
|
74
|
+
def get_dog(self, dog_id: UUID) -> dict[str, Any]:
|
|
75
|
+
dog: Dog = self.repository.get(dog_id)
|
|
70
76
|
return {'name': dog.name, 'tricks': tuple(dog.tricks)}
|
|
71
77
|
```
|
|
72
78
|
|
|
73
79
|
Write a test.
|
|
74
80
|
|
|
75
81
|
```python
|
|
76
|
-
def test_dog_school():
|
|
82
|
+
def test_dog_school() -> None:
|
|
77
83
|
# Construct application object.
|
|
78
84
|
school = DogSchool()
|
|
79
85
|
|
|
@@ -105,7 +111,7 @@ Configure the application to run with an SQLite database. Other persistence modu
|
|
|
105
111
|
import os
|
|
106
112
|
|
|
107
113
|
os.environ["PERSISTENCE_MODULE"] = 'eventsourcing.sqlite'
|
|
108
|
-
os.environ["SQLITE_DBNAME"] = '
|
|
114
|
+
os.environ["SQLITE_DBNAME"] = ':memory:'
|
|
109
115
|
```
|
|
110
116
|
|
|
111
117
|
Run the test with SQLite.
|
|
@@ -22,14 +22,15 @@ from warnings import warn
|
|
|
22
22
|
|
|
23
23
|
from eventsourcing.domain import (
|
|
24
24
|
Aggregate,
|
|
25
|
+
BaseAggregate,
|
|
25
26
|
CanMutateProtocol,
|
|
26
27
|
CollectEventsProtocol,
|
|
27
28
|
DomainEventProtocol,
|
|
28
29
|
EventSourcingError,
|
|
29
30
|
MutableOrImmutableAggregate,
|
|
30
31
|
SDomainEvent,
|
|
31
|
-
Snapshot,
|
|
32
32
|
SnapshotProtocol,
|
|
33
|
+
TAggregateID,
|
|
33
34
|
TDomainEvent,
|
|
34
35
|
TMutableOrImmutableAggregate,
|
|
35
36
|
datetime_now_with_tzinfo,
|
|
@@ -70,7 +71,7 @@ class ProgrammingError(Exception):
|
|
|
70
71
|
|
|
71
72
|
def project_aggregate(
|
|
72
73
|
aggregate: TMutableOrImmutableAggregate | None,
|
|
73
|
-
domain_events: Iterable[DomainEventProtocol],
|
|
74
|
+
domain_events: Iterable[DomainEventProtocol[Any]],
|
|
74
75
|
) -> TMutableOrImmutableAggregate | None:
|
|
75
76
|
"""Projector function for aggregate projections, which works
|
|
76
77
|
by successively calling aggregate mutator function mutate()
|
|
@@ -199,7 +200,7 @@ class LRUCache(Cache[S, T]):
|
|
|
199
200
|
return evicted_key, evicted_value
|
|
200
201
|
|
|
201
202
|
|
|
202
|
-
class Repository:
|
|
203
|
+
class Repository(Generic[TAggregateID]):
|
|
203
204
|
"""Reconstructs aggregates from events in an
|
|
204
205
|
:class:`~eventsourcing.persistence.EventStore`,
|
|
205
206
|
possibly using snapshot store to avoid replaying
|
|
@@ -210,9 +211,9 @@ class Repository:
|
|
|
210
211
|
|
|
211
212
|
def __init__(
|
|
212
213
|
self,
|
|
213
|
-
event_store: EventStore,
|
|
214
|
+
event_store: EventStore[TAggregateID],
|
|
214
215
|
*,
|
|
215
|
-
snapshot_store: EventStore | None = None,
|
|
216
|
+
snapshot_store: EventStore[TAggregateID] | None = None,
|
|
216
217
|
cache_maxsize: int | None = None,
|
|
217
218
|
fastforward: bool = True,
|
|
218
219
|
fastforward_skipping: bool = False,
|
|
@@ -225,11 +226,13 @@ class Repository:
|
|
|
225
226
|
:class:`~eventsourcing.persistence.EventStore` for aggregate
|
|
226
227
|
:class:`~eventsourcing.domain.Snapshot` objects).
|
|
227
228
|
"""
|
|
228
|
-
self.event_store = event_store
|
|
229
|
-
self.snapshot_store = snapshot_store
|
|
229
|
+
self.event_store: EventStore[TAggregateID] = event_store
|
|
230
|
+
self.snapshot_store: EventStore[TAggregateID] | None = snapshot_store
|
|
230
231
|
|
|
231
232
|
if cache_maxsize is None:
|
|
232
|
-
self.cache:
|
|
233
|
+
self.cache: (
|
|
234
|
+
Cache[TAggregateID, MutableOrImmutableAggregate[TAggregateID]] | None
|
|
235
|
+
) = None
|
|
233
236
|
elif cache_maxsize <= 0:
|
|
234
237
|
self.cache = Cache()
|
|
235
238
|
else:
|
|
@@ -240,14 +243,14 @@ class Repository:
|
|
|
240
243
|
|
|
241
244
|
# Because fast-forwarding a cached aggregate isn't thread-safe.
|
|
242
245
|
self._fastforward_locks_lock = Lock()
|
|
243
|
-
self._fastforward_locks_cache: LRUCache[
|
|
246
|
+
self._fastforward_locks_cache: LRUCache[TAggregateID, Lock] = LRUCache(
|
|
244
247
|
maxsize=self.FASTFORWARD_LOCKS_CACHE_MAXSIZE
|
|
245
248
|
)
|
|
246
|
-
self._fastforward_locks_inuse: dict[
|
|
249
|
+
self._fastforward_locks_inuse: dict[TAggregateID, tuple[Lock, int]] = {}
|
|
247
250
|
|
|
248
251
|
def get(
|
|
249
252
|
self,
|
|
250
|
-
aggregate_id:
|
|
253
|
+
aggregate_id: TAggregateID,
|
|
251
254
|
*,
|
|
252
255
|
version: int | None = None,
|
|
253
256
|
projector_func: ProjectorFunction[
|
|
@@ -310,7 +313,7 @@ class Repository:
|
|
|
310
313
|
|
|
311
314
|
def _reconstruct_aggregate(
|
|
312
315
|
self,
|
|
313
|
-
aggregate_id:
|
|
316
|
+
aggregate_id: TAggregateID,
|
|
314
317
|
version: int | None,
|
|
315
318
|
projector_func: ProjectorFunction[TMutableOrImmutableAggregate, TDomainEvent],
|
|
316
319
|
) -> TMutableOrImmutableAggregate:
|
|
@@ -350,11 +353,12 @@ class Repository:
|
|
|
350
353
|
|
|
351
354
|
# Raise exception if "not found".
|
|
352
355
|
if aggregate is None:
|
|
353
|
-
|
|
356
|
+
msg = f"Aggregate {aggregate_id!r} version {version!r} not found."
|
|
357
|
+
raise AggregateNotFoundError(msg)
|
|
354
358
|
# Return the aggregate.
|
|
355
359
|
return aggregate
|
|
356
360
|
|
|
357
|
-
def _use_fastforward_lock(self, aggregate_id:
|
|
361
|
+
def _use_fastforward_lock(self, aggregate_id: TAggregateID) -> Lock:
|
|
358
362
|
lock: Lock | None = None
|
|
359
363
|
with self._fastforward_locks_lock:
|
|
360
364
|
num_users = 0
|
|
@@ -369,7 +373,7 @@ class Repository:
|
|
|
369
373
|
self._fastforward_locks_inuse[aggregate_id] = (lock, num_users)
|
|
370
374
|
return lock
|
|
371
375
|
|
|
372
|
-
def _disuse_fastforward_lock(self, aggregate_id:
|
|
376
|
+
def _disuse_fastforward_lock(self, aggregate_id: TAggregateID) -> None:
|
|
373
377
|
with self._fastforward_locks_lock:
|
|
374
378
|
lock_, num_users = self._fastforward_locks_inuse[aggregate_id]
|
|
375
379
|
num_users -= 1
|
|
@@ -379,7 +383,7 @@ class Repository:
|
|
|
379
383
|
else:
|
|
380
384
|
self._fastforward_locks_inuse[aggregate_id] = (lock_, num_users)
|
|
381
385
|
|
|
382
|
-
def __contains__(self, item:
|
|
386
|
+
def __contains__(self, item: TAggregateID) -> bool:
|
|
383
387
|
"""Tests to see if an aggregate exists in the repository."""
|
|
384
388
|
try:
|
|
385
389
|
self.get(aggregate_id=item)
|
|
@@ -409,7 +413,7 @@ class Section:
|
|
|
409
413
|
"""
|
|
410
414
|
|
|
411
415
|
id: str | None
|
|
412
|
-
items:
|
|
416
|
+
items: Sequence[Notification]
|
|
413
417
|
next_id: str | None
|
|
414
418
|
|
|
415
419
|
|
|
@@ -432,7 +436,7 @@ class NotificationLog(ABC):
|
|
|
432
436
|
topics: Sequence[str] = (),
|
|
433
437
|
*,
|
|
434
438
|
inclusive_of_start: bool = True,
|
|
435
|
-
) ->
|
|
439
|
+
) -> Sequence[Notification]:
|
|
436
440
|
"""Returns a selection of
|
|
437
441
|
:class:`~eventsourcing.persistence.Notification` objects
|
|
438
442
|
from the notification log.
|
|
@@ -518,7 +522,7 @@ class LocalNotificationLog(NotificationLog):
|
|
|
518
522
|
topics: Sequence[str] = (),
|
|
519
523
|
*,
|
|
520
524
|
inclusive_of_start: bool = True,
|
|
521
|
-
) ->
|
|
525
|
+
) -> Sequence[Notification]:
|
|
522
526
|
"""Returns a selection of
|
|
523
527
|
:class:`~eventsourcing.persistence.Notification` objects
|
|
524
528
|
from the notification log.
|
|
@@ -541,7 +545,7 @@ class LocalNotificationLog(NotificationLog):
|
|
|
541
545
|
return f"{first_id},{last_id}"
|
|
542
546
|
|
|
543
547
|
|
|
544
|
-
class ProcessingEvent:
|
|
548
|
+
class ProcessingEvent(Generic[TAggregateID]):
|
|
545
549
|
"""Keeps together a :class:`~eventsourcing.persistence.Tracking`
|
|
546
550
|
object, which represents the position of a domain event notification
|
|
547
551
|
in the notification log of a particular application, and the
|
|
@@ -551,13 +555,17 @@ class ProcessingEvent:
|
|
|
551
555
|
def __init__(self, tracking: Tracking | None = None):
|
|
552
556
|
"""Initialises the process event with the given tracking object."""
|
|
553
557
|
self.tracking = tracking
|
|
554
|
-
self.events: list[DomainEventProtocol] = []
|
|
555
|
-
self.aggregates: dict[
|
|
558
|
+
self.events: list[DomainEventProtocol[TAggregateID]] = []
|
|
559
|
+
self.aggregates: dict[
|
|
560
|
+
TAggregateID, MutableOrImmutableAggregate[TAggregateID]
|
|
561
|
+
] = {}
|
|
556
562
|
self.saved_kwargs: dict[Any, Any] = {}
|
|
557
563
|
|
|
558
564
|
def collect_events(
|
|
559
565
|
self,
|
|
560
|
-
*objs: MutableOrImmutableAggregate
|
|
566
|
+
*objs: MutableOrImmutableAggregate[TAggregateID]
|
|
567
|
+
| DomainEventProtocol[TAggregateID]
|
|
568
|
+
| None,
|
|
561
569
|
**kwargs: Any,
|
|
562
570
|
) -> None:
|
|
563
571
|
"""Collects pending domain events from the given aggregate."""
|
|
@@ -576,7 +584,9 @@ class ProcessingEvent:
|
|
|
576
584
|
|
|
577
585
|
def save(
|
|
578
586
|
self,
|
|
579
|
-
*aggregates: MutableOrImmutableAggregate
|
|
587
|
+
*aggregates: MutableOrImmutableAggregate[TAggregateID]
|
|
588
|
+
| DomainEventProtocol[TAggregateID]
|
|
589
|
+
| None,
|
|
580
590
|
**kwargs: Any,
|
|
581
591
|
) -> None:
|
|
582
592
|
warn(
|
|
@@ -588,17 +598,22 @@ class ProcessingEvent:
|
|
|
588
598
|
self.collect_events(*aggregates, **kwargs)
|
|
589
599
|
|
|
590
600
|
|
|
591
|
-
class Application:
|
|
601
|
+
class Application(Generic[TAggregateID]):
|
|
592
602
|
"""Base class for event-sourced applications."""
|
|
593
603
|
|
|
594
604
|
name = "Application"
|
|
595
605
|
env: ClassVar[dict[str, str]] = {}
|
|
596
606
|
is_snapshotting_enabled: bool = False
|
|
597
|
-
snapshotting_intervals: ClassVar[
|
|
607
|
+
snapshotting_intervals: ClassVar[
|
|
608
|
+
dict[type[MutableOrImmutableAggregate[Any]], int]
|
|
609
|
+
] = {}
|
|
598
610
|
snapshotting_projectors: ClassVar[
|
|
599
|
-
dict[
|
|
611
|
+
dict[
|
|
612
|
+
type[MutableOrImmutableAggregate[Any]],
|
|
613
|
+
ProjectorFunction[Any, Any],
|
|
614
|
+
]
|
|
600
615
|
] = {}
|
|
601
|
-
snapshot_class: type[SnapshotProtocol] =
|
|
616
|
+
snapshot_class: type[SnapshotProtocol[TAggregateID]] | None = None
|
|
602
617
|
log_section_size = 10
|
|
603
618
|
notify_topics: Sequence[str] = []
|
|
604
619
|
|
|
@@ -622,18 +637,18 @@ class Application:
|
|
|
622
637
|
"""
|
|
623
638
|
self.env = self.construct_env(self.name, env) # type: ignore[misc]
|
|
624
639
|
self.factory = self.construct_factory(self.env)
|
|
625
|
-
self.mapper = self.construct_mapper()
|
|
640
|
+
self.mapper: Mapper[TAggregateID] = self.construct_mapper()
|
|
626
641
|
self.recorder = self.construct_recorder()
|
|
627
|
-
self.events = self.construct_event_store()
|
|
628
|
-
self.snapshots: EventStore | None = None
|
|
642
|
+
self.events: EventStore[TAggregateID] = self.construct_event_store()
|
|
643
|
+
self.snapshots: EventStore[TAggregateID] | None = None
|
|
629
644
|
if self.factory.is_snapshotting_enabled():
|
|
630
645
|
self.snapshots = self.construct_snapshot_store()
|
|
631
|
-
self._repository = self.construct_repository()
|
|
646
|
+
self._repository: Repository[TAggregateID] = self.construct_repository()
|
|
632
647
|
self._notification_log = self.construct_notification_log()
|
|
633
648
|
self.closing = Event()
|
|
634
649
|
|
|
635
650
|
@property
|
|
636
|
-
def repository(self) -> Repository:
|
|
651
|
+
def repository(self) -> Repository[TAggregateID]:
|
|
637
652
|
"""An application's repository reconstructs aggregates from stored events."""
|
|
638
653
|
return self._repository
|
|
639
654
|
|
|
@@ -670,7 +685,7 @@ class Application:
|
|
|
670
685
|
"""
|
|
671
686
|
return InfrastructureFactory.construct(env)
|
|
672
687
|
|
|
673
|
-
def construct_mapper(self) -> Mapper:
|
|
688
|
+
def construct_mapper(self) -> Mapper[TAggregateID]:
|
|
674
689
|
"""Constructs a :class:`~eventsourcing.persistence.Mapper`
|
|
675
690
|
for use by the application.
|
|
676
691
|
"""
|
|
@@ -699,7 +714,7 @@ class Application:
|
|
|
699
714
|
"""
|
|
700
715
|
return self.factory.application_recorder()
|
|
701
716
|
|
|
702
|
-
def construct_event_store(self) -> EventStore:
|
|
717
|
+
def construct_event_store(self) -> EventStore[TAggregateID]:
|
|
703
718
|
"""Constructs an :class:`~eventsourcing.persistence.EventStore`
|
|
704
719
|
for use by the application to store and retrieve aggregate
|
|
705
720
|
:class:`~eventsourcing.domain.AggregateEvent` objects.
|
|
@@ -709,8 +724,8 @@ class Application:
|
|
|
709
724
|
recorder=self.recorder,
|
|
710
725
|
)
|
|
711
726
|
|
|
712
|
-
def construct_snapshot_store(self) -> EventStore:
|
|
713
|
-
"""Constructs an :class:`~eventsourcing.persistence.EventStore`
|
|
727
|
+
def construct_snapshot_store(self) -> EventStore[TAggregateID]:
|
|
728
|
+
"""Constructs an :py:class:`~eventsourcing.persistence.EventStore`
|
|
714
729
|
for use by the application to store and retrieve aggregate
|
|
715
730
|
:class:`~eventsourcing.domain.Snapshot` objects.
|
|
716
731
|
"""
|
|
@@ -720,8 +735,8 @@ class Application:
|
|
|
720
735
|
recorder=recorder,
|
|
721
736
|
)
|
|
722
737
|
|
|
723
|
-
def construct_repository(self) -> Repository:
|
|
724
|
-
"""Constructs a :class:`Repository` for use by the application."""
|
|
738
|
+
def construct_repository(self) -> Repository[TAggregateID]:
|
|
739
|
+
"""Constructs a :py:class:`Repository` for use by the application."""
|
|
725
740
|
cache_maxsize_envvar = self.env.get(self.AGGREGATE_CACHE_MAXSIZE)
|
|
726
741
|
cache_maxsize = int(cache_maxsize_envvar) if cache_maxsize_envvar else None
|
|
727
742
|
return Repository(
|
|
@@ -743,13 +758,15 @@ class Application:
|
|
|
743
758
|
|
|
744
759
|
def save(
|
|
745
760
|
self,
|
|
746
|
-
*objs: MutableOrImmutableAggregate
|
|
761
|
+
*objs: MutableOrImmutableAggregate[TAggregateID]
|
|
762
|
+
| DomainEventProtocol[TAggregateID]
|
|
763
|
+
| None,
|
|
747
764
|
**kwargs: Any,
|
|
748
|
-
) -> list[Recording]:
|
|
765
|
+
) -> list[Recording[TAggregateID]]:
|
|
749
766
|
"""Collects pending events from given aggregates and
|
|
750
767
|
puts them in the application's event store.
|
|
751
768
|
"""
|
|
752
|
-
processing_event = ProcessingEvent()
|
|
769
|
+
processing_event: ProcessingEvent[TAggregateID] = ProcessingEvent()
|
|
753
770
|
processing_event.collect_events(*objs, **kwargs)
|
|
754
771
|
recordings = self._record(processing_event)
|
|
755
772
|
self._take_snapshots(processing_event)
|
|
@@ -757,7 +774,9 @@ class Application:
|
|
|
757
774
|
self.notify(processing_event.events) # Deprecated.
|
|
758
775
|
return recordings
|
|
759
776
|
|
|
760
|
-
def _record(
|
|
777
|
+
def _record(
|
|
778
|
+
self, processing_event: ProcessingEvent[TAggregateID]
|
|
779
|
+
) -> list[Recording[TAggregateID]]:
|
|
761
780
|
"""Records given process event in the application's recorder."""
|
|
762
781
|
recordings = self.events.put(
|
|
763
782
|
processing_event.events,
|
|
@@ -769,7 +788,7 @@ class Application:
|
|
|
769
788
|
self.repository.cache.put(aggregate_id, aggregate)
|
|
770
789
|
return recordings
|
|
771
790
|
|
|
772
|
-
def _take_snapshots(self, processing_event: ProcessingEvent) -> None:
|
|
791
|
+
def _take_snapshots(self, processing_event: ProcessingEvent[TAggregateID]) -> None:
|
|
773
792
|
# Take snapshots using IDs and types.
|
|
774
793
|
if self.snapshots and self.snapshotting_intervals:
|
|
775
794
|
for event in processing_event.events:
|
|
@@ -782,22 +801,21 @@ class Application:
|
|
|
782
801
|
try:
|
|
783
802
|
projector_func = self.snapshotting_projectors[type(aggregate)]
|
|
784
803
|
except KeyError:
|
|
804
|
+
if not isinstance(event, CanMutateProtocol):
|
|
805
|
+
msg = (
|
|
806
|
+
f"Cannot take snapshot for {type(aggregate)} with "
|
|
807
|
+
"default project_aggregate() function, because its "
|
|
808
|
+
f"domain event {type(event)} does not implement "
|
|
809
|
+
"the 'can mutate' protocol (see CanMutateProtocol)."
|
|
810
|
+
f" Please define application class {type(self)}"
|
|
811
|
+
" with class variable 'snapshotting_projectors', "
|
|
812
|
+
f"to be a dict that has {type(aggregate)} as a key "
|
|
813
|
+
"with the aggregate projector function for "
|
|
814
|
+
f"{type(aggregate)} as the value for that key."
|
|
815
|
+
)
|
|
816
|
+
raise ProgrammingError(msg) from None
|
|
817
|
+
|
|
785
818
|
projector_func = project_aggregate
|
|
786
|
-
if projector_func is project_aggregate and not isinstance(
|
|
787
|
-
event, CanMutateProtocol
|
|
788
|
-
):
|
|
789
|
-
msg = (
|
|
790
|
-
f"Cannot take snapshot for {type(aggregate)} with "
|
|
791
|
-
"default project_aggregate() function, because its "
|
|
792
|
-
f"domain event {type(event)} does not implement "
|
|
793
|
-
"the 'can mutate' protocol (see CanMutateProtocol)."
|
|
794
|
-
f" Please define application class {type(self)}"
|
|
795
|
-
" with class variable 'snapshotting_projectors', "
|
|
796
|
-
f"to be a dict that has {type(aggregate)} as a key "
|
|
797
|
-
"with the aggregate projector function for "
|
|
798
|
-
f"{type(aggregate)} as the value for that key."
|
|
799
|
-
)
|
|
800
|
-
raise ProgrammingError(msg)
|
|
801
819
|
self.take_snapshot(
|
|
802
820
|
aggregate_id=event.originator_id,
|
|
803
821
|
version=event.originator_version,
|
|
@@ -806,11 +824,9 @@ class Application:
|
|
|
806
824
|
|
|
807
825
|
def take_snapshot(
|
|
808
826
|
self,
|
|
809
|
-
aggregate_id:
|
|
827
|
+
aggregate_id: TAggregateID,
|
|
810
828
|
version: int | None = None,
|
|
811
|
-
projector_func: ProjectorFunction[
|
|
812
|
-
TMutableOrImmutableAggregate, TDomainEvent
|
|
813
|
-
] = project_aggregate,
|
|
829
|
+
projector_func: ProjectorFunction[Any, Any] = project_aggregate,
|
|
814
830
|
) -> None:
|
|
815
831
|
"""Takes a snapshot of the recorded state of the aggregate,
|
|
816
832
|
and puts the snapshot in the snapshot store.
|
|
@@ -824,14 +840,22 @@ class Application:
|
|
|
824
840
|
"application class."
|
|
825
841
|
)
|
|
826
842
|
raise AssertionError(msg)
|
|
827
|
-
aggregate = self.repository.get(
|
|
843
|
+
aggregate: BaseAggregate[TAggregateID] = self.repository.get(
|
|
828
844
|
aggregate_id, version=version, projector_func=projector_func
|
|
829
845
|
)
|
|
830
846
|
snapshot_class = getattr(type(aggregate), "Snapshot", type(self).snapshot_class)
|
|
847
|
+
if snapshot_class is None:
|
|
848
|
+
msg = (
|
|
849
|
+
"Neither application nor aggregate have a snapshot class. "
|
|
850
|
+
f"Please either define a nested 'Snapshot' class on {type(aggregate)} "
|
|
851
|
+
f"or set class attribute 'snapshot_class' on {type(self)}."
|
|
852
|
+
)
|
|
853
|
+
raise AssertionError(msg)
|
|
854
|
+
|
|
831
855
|
snapshot = snapshot_class.take(aggregate)
|
|
832
856
|
self.snapshots.put([snapshot])
|
|
833
857
|
|
|
834
|
-
def notify(self, new_events: list[DomainEventProtocol]) -> None:
|
|
858
|
+
def notify(self, new_events: list[DomainEventProtocol[TAggregateID]]) -> None:
|
|
835
859
|
"""Deprecated.
|
|
836
860
|
|
|
837
861
|
Called after new aggregate events have been saved. This
|
|
@@ -840,7 +864,7 @@ class Application:
|
|
|
840
864
|
need to take action when new domain events have been saved.
|
|
841
865
|
"""
|
|
842
866
|
|
|
843
|
-
def _notify(self, recordings: list[Recording]) -> None:
|
|
867
|
+
def _notify(self, recordings: list[Recording[TAggregateID]]) -> None:
|
|
844
868
|
"""Called after new aggregate events have been saved. This
|
|
845
869
|
method on this class doesn't actually do anything,
|
|
846
870
|
but this method may be implemented by subclasses that
|
|
@@ -852,7 +876,7 @@ class Application:
|
|
|
852
876
|
self.factory.close()
|
|
853
877
|
|
|
854
878
|
|
|
855
|
-
TApplication = TypeVar("TApplication", bound=Application)
|
|
879
|
+
TApplication = TypeVar("TApplication", bound=Application[Any])
|
|
856
880
|
|
|
857
881
|
|
|
858
882
|
class AggregateNotFoundError(EventSourcingError):
|
|
@@ -877,7 +901,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
|
|
|
877
901
|
|
|
878
902
|
def __init__(
|
|
879
903
|
self,
|
|
880
|
-
events: EventStore,
|
|
904
|
+
events: EventStore[Any],
|
|
881
905
|
originator_id: UUID,
|
|
882
906
|
logged_cls: type[TDomainEvent], # TODO: Rename to 'event_class' in v10.
|
|
883
907
|
):
|