eventsourcing 9.3.3__py3-none-any.whl → 9.3.4__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 (127) hide show
  1. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/METADATA +1 -1
  2. eventsourcing-9.3.4.dist-info/RECORD +24 -0
  3. eventsourcing/examples/__init__.py +0 -0
  4. eventsourcing/examples/aggregate1/__init__.py +0 -0
  5. eventsourcing/examples/aggregate1/application.py +0 -27
  6. eventsourcing/examples/aggregate1/domainmodel.py +0 -16
  7. eventsourcing/examples/aggregate1/test_application.py +0 -37
  8. eventsourcing/examples/aggregate2/__init__.py +0 -0
  9. eventsourcing/examples/aggregate2/application.py +0 -27
  10. eventsourcing/examples/aggregate2/domainmodel.py +0 -22
  11. eventsourcing/examples/aggregate2/test_application.py +0 -37
  12. eventsourcing/examples/aggregate3/__init__.py +0 -0
  13. eventsourcing/examples/aggregate3/application.py +0 -27
  14. eventsourcing/examples/aggregate3/domainmodel.py +0 -38
  15. eventsourcing/examples/aggregate3/test_application.py +0 -37
  16. eventsourcing/examples/aggregate4/__init__.py +0 -0
  17. eventsourcing/examples/aggregate4/application.py +0 -27
  18. eventsourcing/examples/aggregate4/domainmodel.py +0 -114
  19. eventsourcing/examples/aggregate4/test_application.py +0 -38
  20. eventsourcing/examples/aggregate5/__init__.py +0 -0
  21. eventsourcing/examples/aggregate5/application.py +0 -27
  22. eventsourcing/examples/aggregate5/domainmodel.py +0 -131
  23. eventsourcing/examples/aggregate5/test_application.py +0 -38
  24. eventsourcing/examples/aggregate6/__init__.py +0 -0
  25. eventsourcing/examples/aggregate6/application.py +0 -30
  26. eventsourcing/examples/aggregate6/domainmodel.py +0 -123
  27. eventsourcing/examples/aggregate6/test_application.py +0 -38
  28. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  29. eventsourcing/examples/aggregate6a/application.py +0 -40
  30. eventsourcing/examples/aggregate6a/domainmodel.py +0 -149
  31. eventsourcing/examples/aggregate6a/test_application.py +0 -45
  32. eventsourcing/examples/aggregate7/__init__.py +0 -0
  33. eventsourcing/examples/aggregate7/application.py +0 -53
  34. eventsourcing/examples/aggregate7/domainmodel.py +0 -142
  35. eventsourcing/examples/aggregate7/persistence.py +0 -57
  36. eventsourcing/examples/aggregate7/test_application.py +0 -45
  37. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -45
  38. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -67
  39. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  40. eventsourcing/examples/aggregate7a/application.py +0 -56
  41. eventsourcing/examples/aggregate7a/domainmodel.py +0 -168
  42. eventsourcing/examples/aggregate7a/test_application.py +0 -46
  43. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -45
  44. eventsourcing/examples/aggregate8/__init__.py +0 -0
  45. eventsourcing/examples/aggregate8/application.py +0 -47
  46. eventsourcing/examples/aggregate8/domainmodel.py +0 -71
  47. eventsourcing/examples/aggregate8/persistence.py +0 -57
  48. eventsourcing/examples/aggregate8/test_application.py +0 -44
  49. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -44
  50. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -38
  51. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  52. eventsourcing/examples/bankaccounts/application.py +0 -70
  53. eventsourcing/examples/bankaccounts/domainmodel.py +0 -56
  54. eventsourcing/examples/bankaccounts/test.py +0 -173
  55. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  56. eventsourcing/examples/cargoshipping/application.py +0 -126
  57. eventsourcing/examples/cargoshipping/domainmodel.py +0 -330
  58. eventsourcing/examples/cargoshipping/interface.py +0 -143
  59. eventsourcing/examples/cargoshipping/test.py +0 -231
  60. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  61. eventsourcing/examples/contentmanagement/application.py +0 -118
  62. eventsourcing/examples/contentmanagement/domainmodel.py +0 -69
  63. eventsourcing/examples/contentmanagement/test.py +0 -180
  64. eventsourcing/examples/contentmanagement/utils.py +0 -26
  65. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  66. eventsourcing/examples/contentmanagementsystem/application.py +0 -54
  67. eventsourcing/examples/contentmanagementsystem/postgres.py +0 -17
  68. eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -17
  69. eventsourcing/examples/contentmanagementsystem/system.py +0 -14
  70. eventsourcing/examples/contentmanagementsystem/test_system.py +0 -180
  71. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  72. eventsourcing/examples/searchablecontent/application.py +0 -45
  73. eventsourcing/examples/searchablecontent/persistence.py +0 -23
  74. eventsourcing/examples/searchablecontent/postgres.py +0 -118
  75. eventsourcing/examples/searchablecontent/sqlite.py +0 -136
  76. eventsourcing/examples/searchablecontent/test_application.py +0 -110
  77. eventsourcing/examples/searchablecontent/test_recorder.py +0 -68
  78. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  79. eventsourcing/examples/searchabletimestamps/application.py +0 -32
  80. eventsourcing/examples/searchabletimestamps/persistence.py +0 -20
  81. eventsourcing/examples/searchabletimestamps/postgres.py +0 -110
  82. eventsourcing/examples/searchabletimestamps/sqlite.py +0 -99
  83. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +0 -94
  84. eventsourcing/examples/test_invoice.py +0 -176
  85. eventsourcing/examples/test_parking_lot.py +0 -206
  86. eventsourcing/tests/application_tests/__init__.py +0 -0
  87. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -55
  88. eventsourcing/tests/application_tests/test_application_with_popo.py +0 -22
  89. eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -75
  90. eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -72
  91. eventsourcing/tests/application_tests/test_cache.py +0 -134
  92. eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -162
  93. eventsourcing/tests/application_tests/test_notificationlog.py +0 -232
  94. eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -126
  95. eventsourcing/tests/application_tests/test_processapplication.py +0 -110
  96. eventsourcing/tests/application_tests/test_processingpolicy.py +0 -109
  97. eventsourcing/tests/application_tests/test_repository.py +0 -504
  98. eventsourcing/tests/application_tests/test_snapshotting.py +0 -68
  99. eventsourcing/tests/application_tests/test_upcasting.py +0 -459
  100. eventsourcing/tests/docs_tests/__init__.py +0 -0
  101. eventsourcing/tests/docs_tests/test_docs.py +0 -293
  102. eventsourcing/tests/domain_tests/__init__.py +0 -0
  103. eventsourcing/tests/domain_tests/test_aggregate.py +0 -1200
  104. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -1604
  105. eventsourcing/tests/domain_tests/test_domainevent.py +0 -80
  106. eventsourcing/tests/interface_tests/__init__.py +0 -0
  107. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -258
  108. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  109. eventsourcing/tests/persistence_tests/test_aes.py +0 -93
  110. eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -722
  111. eventsourcing/tests/persistence_tests/test_eventstore.py +0 -72
  112. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -21
  113. eventsourcing/tests/persistence_tests/test_mapper.py +0 -113
  114. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -69
  115. eventsourcing/tests/persistence_tests/test_popo.py +0 -124
  116. eventsourcing/tests/persistence_tests/test_postgres.py +0 -1120
  117. eventsourcing/tests/persistence_tests/test_sqlite.py +0 -348
  118. eventsourcing/tests/persistence_tests/test_transcoder.py +0 -44
  119. eventsourcing/tests/system_tests/__init__.py +0 -0
  120. eventsourcing/tests/system_tests/test_runner.py +0 -935
  121. eventsourcing/tests/system_tests/test_system.py +0 -284
  122. eventsourcing/tests/utils_tests/__init__.py +0 -0
  123. eventsourcing/tests/utils_tests/test_utils.py +0 -226
  124. eventsourcing-9.3.3.dist-info/RECORD +0 -145
  125. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/AUTHORS +0 -0
  126. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/LICENSE +0 -0
  127. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/WHEEL +0 -0
@@ -1,57 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Any, Dict, cast
4
-
5
- import orjson
6
- from pydantic import BaseModel
7
-
8
- from eventsourcing.persistence import (
9
- Mapper,
10
- ProgrammingError,
11
- StoredEvent,
12
- Transcoder,
13
- Transcoding,
14
- )
15
- from eventsourcing.utils import get_topic, resolve_topic
16
-
17
- if TYPE_CHECKING: # pragma: nocover
18
- from eventsourcing.domain import DomainEventProtocol
19
-
20
-
21
- class PydanticMapper(Mapper):
22
- def to_stored_event(self, domain_event: DomainEventProtocol) -> StoredEvent:
23
- topic = get_topic(domain_event.__class__)
24
- event_state = cast(BaseModel, domain_event).model_dump()
25
- stored_state = self.transcoder.encode(event_state)
26
- if self.compressor:
27
- stored_state = self.compressor.compress(stored_state)
28
- if self.cipher:
29
- stored_state = self.cipher.encrypt(stored_state)
30
- return StoredEvent(
31
- originator_id=domain_event.originator_id,
32
- originator_version=domain_event.originator_version,
33
- topic=topic,
34
- state=stored_state,
35
- )
36
-
37
- def to_domain_event(self, stored: StoredEvent) -> DomainEventProtocol:
38
- stored_state = stored.state
39
- if self.cipher:
40
- stored_state = self.cipher.decrypt(stored_state)
41
- if self.compressor:
42
- stored_state = self.compressor.decompress(stored_state)
43
- event_state: Dict[str, Any] = self.transcoder.decode(stored_state)
44
- cls = resolve_topic(stored.topic)
45
- return cls(**event_state)
46
-
47
-
48
- class OrjsonTranscoder(Transcoder):
49
- def encode(self, obj: Any) -> bytes:
50
- return orjson.dumps(obj)
51
-
52
- def decode(self, data: bytes) -> Any:
53
- return orjson.loads(data)
54
-
55
- def register(self, _: Transcoding) -> None: # pragma: no cover
56
- msg = "Please use Pydantic BaseModel"
57
- raise ProgrammingError(msg)
@@ -1,45 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from unittest import TestCase
5
-
6
- from eventsourcing.examples.aggregate7.application import DogSchool
7
- from eventsourcing.examples.aggregate7.domainmodel import project_dog
8
-
9
-
10
- class TestDogSchool(TestCase):
11
- def test_dog_school(self) -> None:
12
- # Construct application object.
13
- school = DogSchool()
14
-
15
- # Evolve application state.
16
- dog_id = school.register_dog("Fido")
17
- school.add_trick(dog_id, "roll over")
18
- school.add_trick(dog_id, "play dead")
19
-
20
- # Query application state.
21
- dog = school.get_dog(dog_id)
22
- self.assertEqual(dog["name"], "Fido")
23
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
24
- self.assertIsInstance(dog["created_on"], datetime)
25
- self.assertIsInstance(dog["modified_on"], datetime)
26
-
27
- # Select notifications.
28
- notifications = school.notification_log.select(start=1, limit=10)
29
- assert len(notifications) == 3
30
-
31
- # Take snapshot.
32
- school.take_snapshot(dog_id, version=3, projector_func=project_dog)
33
- dog = school.get_dog(dog_id)
34
- self.assertEqual(dog["name"], "Fido")
35
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
36
- self.assertIsInstance(dog["created_on"], datetime)
37
- self.assertIsInstance(dog["modified_on"], datetime)
38
-
39
- # Continue with snapshotted aggregate.
40
- school.add_trick(dog_id, "fetch ball")
41
- dog = school.get_dog(dog_id)
42
- self.assertEqual(dog["name"], "Fido")
43
- self.assertEqual(dog["tricks"], ("roll over", "play dead", "fetch ball"))
44
- self.assertIsInstance(dog["created_on"], datetime)
45
- self.assertIsInstance(dog["modified_on"], datetime)
@@ -1,45 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from unittest import TestCase
4
-
5
- from eventsourcing.cipher import AESCipher
6
- from eventsourcing.examples.aggregate7.application import DogSchool
7
- from eventsourcing.examples.aggregate7.domainmodel import project_dog
8
-
9
-
10
- class TestDogSchool(TestCase):
11
- def test_dog_school(self) -> None:
12
- # Construct application object.
13
- school = DogSchool(
14
- env={
15
- "COMPRESSOR_TOPIC": "eventsourcing.compressor:ZlibCompressor",
16
- "CIPHER_TOPIC": "eventsourcing.cipher:AESCipher",
17
- "CIPHER_KEY": AESCipher.create_key(num_bytes=32),
18
- }
19
- )
20
-
21
- # Evolve application state.
22
- dog_id = school.register_dog("Fido")
23
- school.add_trick(dog_id, "roll over")
24
- school.add_trick(dog_id, "play dead")
25
-
26
- # Query application state.
27
- dog = school.get_dog(dog_id)
28
- assert dog["name"] == "Fido"
29
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
30
-
31
- # Select notifications.
32
- notifications = school.notification_log.select(start=1, limit=10)
33
- assert len(notifications) == 3
34
-
35
- # Take snapshot.
36
- school.take_snapshot(dog_id, version=3, projector_func=project_dog)
37
- dog = school.get_dog(dog_id)
38
- assert dog["name"] == "Fido"
39
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
40
-
41
- # Continue with snapshotted aggregate.
42
- school.add_trick(dog_id, "fetch ball")
43
- dog = school.get_dog(dog_id)
44
- assert dog["name"] == "Fido"
45
- self.assertEqual(dog["tricks"], ("roll over", "play dead", "fetch ball"))
@@ -1,67 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, ClassVar, Dict, Type, cast
4
- from unittest import TestCase
5
-
6
- from eventsourcing.domain import MutableOrImmutableAggregate, ProgrammingError
7
- from eventsourcing.examples.aggregate7.application import DogSchool
8
- from eventsourcing.examples.aggregate7.domainmodel import (
9
- Dog,
10
- Trick,
11
- add_trick,
12
- project_dog,
13
- register_dog,
14
- )
15
-
16
- if TYPE_CHECKING: # pragma: nocover
17
- from uuid import UUID
18
-
19
-
20
- class SubDogSchool(DogSchool):
21
- snapshotting_intervals: ClassVar[
22
- Dict[Type[MutableOrImmutableAggregate], int] | None
23
- ] = {Dog: 1}
24
-
25
- def register_dog(self, name: str) -> UUID:
26
- event = register_dog(name)
27
- dog = project_dog(None, [event])
28
- self.save(dog, event)
29
- return event.originator_id
30
-
31
- def add_trick(self, dog_id: UUID, trick: str) -> None:
32
- dog = self.repository.get(dog_id, projector_func=project_dog)
33
- event = add_trick(dog, Trick(name=trick))
34
- dog = cast(Dog, project_dog(dog, [event]))
35
- self.save(dog, event)
36
-
37
-
38
- class TestDogSchool(TestCase):
39
- def test_dog_school(self) -> None:
40
- # Construct application object.
41
- school = SubDogSchool()
42
-
43
- # Check error when snapshotting_projectors not set.
44
- with self.assertRaises(ProgrammingError) as cm:
45
- school.register_dog("Fido")
46
-
47
- self.assertIn("Cannot take snapshot", cm.exception.args[0])
48
-
49
- # Set snapshotting_projectors.
50
- SubDogSchool.snapshotting_projectors = {Dog: project_dog}
51
-
52
- # Check snapshotting when snapshotting_projectors is set.
53
- dog_id = school.register_dog("Fido")
54
-
55
- assert school.snapshots is not None
56
- self.assertEqual(1, len(list(school.snapshots.get(dog_id))))
57
-
58
- school.add_trick(dog_id, "roll over")
59
- self.assertEqual(2, len(list(school.snapshots.get(dog_id))))
60
-
61
- school.add_trick(dog_id, "play dead")
62
- self.assertEqual(3, len(list(school.snapshots.get(dog_id))))
63
-
64
- # Query application state.
65
- dog = school.get_dog(dog_id)
66
- self.assertEqual(dog["name"], "Fido")
67
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
File without changes
@@ -1,56 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Any, ClassVar, Dict, Type
4
-
5
- from eventsourcing.application import Application, ProjectorFunction
6
- from eventsourcing.examples.aggregate7.persistence import (
7
- OrjsonTranscoder,
8
- PydanticMapper,
9
- )
10
- from eventsourcing.examples.aggregate7a.domainmodel import (
11
- Dog,
12
- Snapshot,
13
- add_trick,
14
- project_dog,
15
- register_dog,
16
- )
17
-
18
- if TYPE_CHECKING: # pragma: nocover
19
- from uuid import UUID
20
-
21
- from eventsourcing.domain import MutableOrImmutableAggregate
22
- from eventsourcing.persistence import Mapper, Transcoder
23
-
24
-
25
- class DogSchool(Application):
26
- is_snapshotting_enabled = True
27
- snapshot_class = Snapshot
28
- snapshotting_intervals: ClassVar[
29
- Dict[Type[MutableOrImmutableAggregate], int] | None
30
- ] = {Dog: 5}
31
- snapshotting_projectors: ClassVar[
32
- Dict[Type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]] | None
33
- ] = {Dog: project_dog}
34
-
35
- def register_dog(self, name: str) -> UUID:
36
- dog = register_dog(name)
37
- self.save(dog)
38
- return dog.id
39
-
40
- def add_trick(self, dog_id: UUID, trick: str) -> None:
41
- dog = self.repository.get(dog_id, projector_func=project_dog)
42
- dog = add_trick(dog, trick)
43
- self.save(dog)
44
-
45
- def get_dog(self, dog_id: UUID) -> Dict[str, Any]:
46
- dog = self.repository.get(dog_id, projector_func=project_dog)
47
- return {"name": dog.name, "tricks": tuple([t.name for t in dog.tricks])}
48
-
49
- def construct_mapper(self) -> Mapper:
50
- return self.factory.mapper(
51
- transcoder=self.construct_transcoder(),
52
- mapper_class=PydanticMapper,
53
- )
54
-
55
- def construct_transcoder(self) -> Transcoder:
56
- return OrjsonTranscoder()
@@ -1,168 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import contextlib
4
- from collections import defaultdict
5
- from datetime import datetime, timezone
6
- from functools import singledispatch
7
- from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar
8
- from uuid import UUID, uuid4
9
-
10
- from pydantic import BaseModel, ConfigDict
11
-
12
- from eventsourcing.utils import get_topic
13
-
14
-
15
- class DomainEvent(BaseModel):
16
- model_config = ConfigDict(frozen=True, extra="forbid")
17
-
18
- originator_id: UUID
19
- originator_version: int
20
- timestamp: datetime
21
-
22
-
23
- def create_timestamp() -> datetime:
24
- return datetime.now(tz=timezone.utc)
25
-
26
-
27
- class Aggregate(BaseModel):
28
- model_config = ConfigDict(frozen=True, extra="forbid")
29
-
30
- id: UUID
31
- version: int
32
- created_on: datetime
33
- modified_on: datetime
34
-
35
- def hold_event(self, event: DomainEvent) -> None:
36
- all_pending_events[id(self)].append(event)
37
-
38
- def collect_events(self) -> List[DomainEvent]:
39
- try:
40
- return all_pending_events.pop(id(self))
41
- except KeyError: # pragma: no cover
42
- return []
43
-
44
- def __del__(self) -> None:
45
- with contextlib.suppress(KeyError):
46
- all_pending_events.pop(id(self))
47
-
48
-
49
- class Snapshot(DomainEvent):
50
- topic: str
51
- state: Dict[str, Any]
52
-
53
- @classmethod
54
- def take(cls, aggregate: Aggregate) -> Snapshot:
55
- return Snapshot(
56
- originator_id=aggregate.id,
57
- originator_version=aggregate.version,
58
- timestamp=create_timestamp(),
59
- topic=get_topic(type(aggregate)),
60
- state=aggregate.model_dump(),
61
- )
62
-
63
-
64
- TAggregate = TypeVar("TAggregate", bound=Aggregate)
65
- MutatorFunction = Callable[..., Optional[TAggregate]]
66
-
67
-
68
- def aggregate_projector(
69
- mutator: MutatorFunction[TAggregate],
70
- ) -> Callable[[TAggregate | None, Iterable[DomainEvent]], TAggregate | None]:
71
- def project_aggregate(
72
- aggregate: TAggregate | None, events: Iterable[DomainEvent]
73
- ) -> TAggregate | None:
74
- for event in events:
75
- aggregate = mutator(event, aggregate)
76
- return aggregate
77
-
78
- return project_aggregate
79
-
80
-
81
- class Trick(BaseModel):
82
- name: str
83
-
84
-
85
- all_pending_events: Dict[int, List[DomainEvent]] = defaultdict(list)
86
-
87
-
88
- class Dog(Aggregate):
89
- name: str
90
- tricks: Tuple[Trick, ...]
91
-
92
-
93
- class DogRegistered(DomainEvent):
94
- name: str
95
-
96
-
97
- class TrickAdded(DomainEvent):
98
- trick: Trick
99
-
100
-
101
- def register_dog(name: str) -> Dog:
102
- event = DogRegistered(
103
- originator_id=uuid4(),
104
- originator_version=1,
105
- timestamp=create_timestamp(),
106
- name=name,
107
- )
108
- dog = mutate_dog(event, None)
109
- assert isinstance(dog, Dog)
110
- dog.hold_event(event)
111
- return dog
112
-
113
-
114
- def add_trick(dog: Dog, trick: str) -> Dog:
115
- event = TrickAdded(
116
- originator_id=dog.id,
117
- originator_version=dog.version + 1,
118
- timestamp=create_timestamp(),
119
- trick=Trick(name=trick),
120
- )
121
- dog_ = mutate_dog(event, dog)
122
- assert isinstance(dog_, Dog)
123
- dog_.hold_event(event)
124
- return dog_
125
-
126
-
127
- @singledispatch
128
- def mutate_dog(_: DomainEvent, __: Dog | None) -> Dog | None:
129
- """Mutates aggregate with event."""
130
-
131
-
132
- @mutate_dog.register
133
- def _(event: DogRegistered, _: None) -> Dog:
134
- return Dog(
135
- id=event.originator_id,
136
- version=event.originator_version,
137
- created_on=event.timestamp,
138
- modified_on=event.timestamp,
139
- name=event.name,
140
- tricks=(),
141
- )
142
-
143
-
144
- @mutate_dog.register
145
- def _(event: TrickAdded, dog: Dog) -> Dog:
146
- return Dog(
147
- id=dog.id,
148
- version=event.originator_version,
149
- created_on=dog.created_on,
150
- modified_on=event.timestamp,
151
- name=dog.name,
152
- tricks=(*dog.tricks, event.trick),
153
- )
154
-
155
-
156
- @mutate_dog.register
157
- def _(event: Snapshot, _: None) -> Dog:
158
- return Dog(
159
- id=event.state["id"],
160
- version=event.state["version"],
161
- created_on=event.state["created_on"],
162
- modified_on=event.state["modified_on"],
163
- name=event.state["name"],
164
- tricks=event.state["tricks"],
165
- )
166
-
167
-
168
- project_dog = aggregate_projector(mutate_dog)
@@ -1,46 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from unittest import TestCase
4
-
5
- from eventsourcing.examples.aggregate7a.application import DogSchool
6
- from eventsourcing.examples.aggregate7a.domainmodel import project_dog
7
-
8
-
9
- class TestDogSchool(TestCase):
10
- def test_dog_school(self) -> None:
11
- # Construct application object.
12
- school = DogSchool()
13
-
14
- # Evolve application state.
15
- dog_id = school.register_dog("Fido")
16
- school.add_trick(dog_id, "roll over")
17
- school.add_trick(dog_id, "play dead")
18
-
19
- # Query application state.
20
- dog = school.get_dog(dog_id)
21
- self.assertEqual(dog["name"], "Fido")
22
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
23
-
24
- # Select notifications.
25
- notifications = school.notification_log.select(start=1, limit=10)
26
- assert len(notifications) == 3
27
-
28
- # Take snapshot.
29
- assert school.snapshots is not None
30
- assert len(list(school.snapshots.get(dog_id))) == 0
31
- school.take_snapshot(dog_id, version=3, projector_func=project_dog)
32
- assert len(list(school.snapshots.get(dog_id))) == 1
33
- dog = school.get_dog(dog_id)
34
- self.assertEqual(dog["name"], "Fido")
35
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
36
-
37
- # Continue with snapshotted aggregate.
38
- school.add_trick(dog_id, "fetch ball")
39
- dog = school.get_dog(dog_id)
40
- self.assertEqual(dog["name"], "Fido")
41
- self.assertEqual(dog["tricks"], ("roll over", "play dead", "fetch ball"))
42
-
43
- # Auto-snapshotting at version 5.
44
- assert len(list(school.snapshots.get(dog_id))) == 1
45
- school.add_trick(dog_id, "jump hoop")
46
- assert len(list(school.snapshots.get(dog_id))) == 2
@@ -1,45 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from unittest import TestCase
4
-
5
- from eventsourcing.cipher import AESCipher
6
- from eventsourcing.examples.aggregate7a.application import DogSchool
7
- from eventsourcing.examples.aggregate7a.domainmodel import project_dog
8
-
9
-
10
- class TestDogSchool(TestCase):
11
- def test_dog_school(self) -> None:
12
- # Construct application object.
13
- school = DogSchool(
14
- env={
15
- "COMPRESSOR_TOPIC": "eventsourcing.compressor:ZlibCompressor",
16
- "CIPHER_TOPIC": "eventsourcing.cipher:AESCipher",
17
- "CIPHER_KEY": AESCipher.create_key(num_bytes=32),
18
- }
19
- )
20
-
21
- # Evolve application state.
22
- dog_id = school.register_dog("Fido")
23
- school.add_trick(dog_id, "roll over")
24
- school.add_trick(dog_id, "play dead")
25
-
26
- # Query application state.
27
- dog = school.get_dog(dog_id)
28
- assert dog["name"] == "Fido"
29
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
30
-
31
- # Select notifications.
32
- notifications = school.notification_log.select(start=1, limit=10)
33
- assert len(notifications) == 3
34
-
35
- # Take snapshot.
36
- school.take_snapshot(dog_id, version=3, projector_func=project_dog)
37
- dog = school.get_dog(dog_id)
38
- assert dog["name"] == "Fido"
39
- self.assertEqual(dog["tricks"], ("roll over", "play dead"))
40
-
41
- # Continue with snapshotted aggregate.
42
- school.add_trick(dog_id, "fetch ball")
43
- dog = school.get_dog(dog_id)
44
- assert dog["name"] == "Fido"
45
- self.assertEqual(dog["tricks"], ("roll over", "play dead", "fetch ball"))
File without changes
@@ -1,47 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Any, Dict
4
-
5
- from eventsourcing.application import Application
6
- from eventsourcing.examples.aggregate8.domainmodel import Dog, Trick
7
- from eventsourcing.examples.aggregate8.persistence import (
8
- OrjsonTranscoder,
9
- PydanticMapper,
10
- )
11
-
12
- if TYPE_CHECKING: # pragma: nocover
13
- from uuid import UUID
14
-
15
- from eventsourcing.persistence import Mapper, Transcoder
16
-
17
-
18
- class DogSchool(Application):
19
- is_snapshotting_enabled = True
20
-
21
- def register_dog(self, name: str) -> UUID:
22
- dog = Dog(name)
23
- self.save(dog)
24
- return dog.id
25
-
26
- def add_trick(self, dog_id: UUID, trick: str) -> None:
27
- dog: Dog = self.repository.get(dog_id)
28
- dog.add_trick(Trick(name=trick))
29
- self.save(dog)
30
-
31
- def get_dog(self, dog_id: UUID) -> Dict[str, Any]:
32
- dog: Dog = self.repository.get(dog_id)
33
- return {
34
- "name": dog.name,
35
- "tricks": tuple([t.name for t in dog.tricks]),
36
- "created_on": dog.created_on,
37
- "modified_on": dog.modified_on,
38
- }
39
-
40
- def construct_mapper(self) -> Mapper:
41
- return self.factory.mapper(
42
- transcoder=self.construct_transcoder(),
43
- mapper_class=PydanticMapper,
44
- )
45
-
46
- def construct_transcoder(self) -> Transcoder:
47
- return OrjsonTranscoder()
@@ -1,71 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from typing import Any, List
5
- from uuid import UUID
6
-
7
- from pydantic import BaseModel, ConfigDict, TypeAdapter
8
-
9
- from eventsourcing.domain import (
10
- Aggregate as BaseAggregate,
11
- CanInitAggregate,
12
- CanMutateAggregate,
13
- CanSnapshotAggregate,
14
- event,
15
- )
16
-
17
-
18
- class DomainEvent(BaseModel):
19
- model_config = ConfigDict(frozen=True, extra="forbid")
20
-
21
- originator_id: UUID
22
- originator_version: int
23
- timestamp: datetime
24
-
25
-
26
- datetime_adapter = TypeAdapter(datetime)
27
-
28
-
29
- class SnapshotState(BaseModel):
30
- model_config = ConfigDict(frozen=True, extra="allow")
31
-
32
- def __init__(self, **kwargs: Any) -> None:
33
- for key in ["_created_on", "_modified_on"]:
34
- kwargs[key] = datetime_adapter.validate_python(kwargs[key])
35
- super().__init__(**kwargs)
36
-
37
-
38
- class AggregateSnapshot(DomainEvent, CanSnapshotAggregate):
39
- topic: str
40
- state: SnapshotState
41
-
42
-
43
- class Aggregate(BaseAggregate):
44
- class Event(DomainEvent, CanMutateAggregate):
45
- pass
46
-
47
- class Created(Event, CanInitAggregate):
48
- originator_topic: str
49
-
50
-
51
- class Trick(BaseModel):
52
- name: str
53
-
54
-
55
- class DogSnapshotState(SnapshotState):
56
- name: str
57
- tricks: List[Trick]
58
-
59
-
60
- class Dog(Aggregate):
61
- class Snapshot(AggregateSnapshot):
62
- state: DogSnapshotState
63
-
64
- @event("Registered")
65
- def __init__(self, name: str) -> None:
66
- self.name = name
67
- self.tricks: List[Trick] = []
68
-
69
- @event("TrickAdded")
70
- def add_trick(self, trick: Trick) -> None:
71
- self.tricks.append(trick)