eventsourcing 9.2.21__py3-none-any.whl → 9.3.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.

Files changed (145) hide show
  1. eventsourcing/__init__.py +1 -1
  2. eventsourcing/application.py +137 -132
  3. eventsourcing/cipher.py +17 -12
  4. eventsourcing/compressor.py +2 -0
  5. eventsourcing/dispatch.py +30 -56
  6. eventsourcing/domain.py +221 -227
  7. eventsourcing/examples/__init__.py +0 -0
  8. eventsourcing/examples/aggregate1/__init__.py +0 -0
  9. eventsourcing/examples/aggregate1/application.py +27 -0
  10. eventsourcing/examples/aggregate1/domainmodel.py +16 -0
  11. eventsourcing/examples/aggregate1/test_application.py +37 -0
  12. eventsourcing/examples/aggregate2/__init__.py +0 -0
  13. eventsourcing/examples/aggregate2/application.py +27 -0
  14. eventsourcing/examples/aggregate2/domainmodel.py +22 -0
  15. eventsourcing/examples/aggregate2/test_application.py +37 -0
  16. eventsourcing/examples/aggregate3/__init__.py +0 -0
  17. eventsourcing/examples/aggregate3/application.py +27 -0
  18. eventsourcing/examples/aggregate3/domainmodel.py +38 -0
  19. eventsourcing/examples/aggregate3/test_application.py +37 -0
  20. eventsourcing/examples/aggregate4/__init__.py +0 -0
  21. eventsourcing/examples/aggregate4/application.py +27 -0
  22. eventsourcing/examples/aggregate4/domainmodel.py +114 -0
  23. eventsourcing/examples/aggregate4/test_application.py +38 -0
  24. eventsourcing/examples/aggregate5/__init__.py +0 -0
  25. eventsourcing/examples/aggregate5/application.py +27 -0
  26. eventsourcing/examples/aggregate5/domainmodel.py +131 -0
  27. eventsourcing/examples/aggregate5/test_application.py +38 -0
  28. eventsourcing/examples/aggregate6/__init__.py +0 -0
  29. eventsourcing/examples/aggregate6/application.py +30 -0
  30. eventsourcing/examples/aggregate6/domainmodel.py +123 -0
  31. eventsourcing/examples/aggregate6/test_application.py +38 -0
  32. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  33. eventsourcing/examples/aggregate6a/application.py +40 -0
  34. eventsourcing/examples/aggregate6a/domainmodel.py +149 -0
  35. eventsourcing/examples/aggregate6a/test_application.py +45 -0
  36. eventsourcing/examples/aggregate7/__init__.py +0 -0
  37. eventsourcing/examples/aggregate7/application.py +48 -0
  38. eventsourcing/examples/aggregate7/domainmodel.py +144 -0
  39. eventsourcing/examples/aggregate7/persistence.py +57 -0
  40. eventsourcing/examples/aggregate7/test_application.py +38 -0
  41. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +45 -0
  42. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +67 -0
  43. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  44. eventsourcing/examples/aggregate7a/application.py +56 -0
  45. eventsourcing/examples/aggregate7a/domainmodel.py +170 -0
  46. eventsourcing/examples/aggregate7a/test_application.py +46 -0
  47. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +45 -0
  48. eventsourcing/examples/aggregate8/__init__.py +0 -0
  49. eventsourcing/examples/aggregate8/application.py +47 -0
  50. eventsourcing/examples/aggregate8/domainmodel.py +65 -0
  51. eventsourcing/examples/aggregate8/persistence.py +57 -0
  52. eventsourcing/examples/aggregate8/test_application.py +37 -0
  53. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +44 -0
  54. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +38 -0
  55. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  56. eventsourcing/examples/bankaccounts/application.py +70 -0
  57. eventsourcing/examples/bankaccounts/domainmodel.py +56 -0
  58. eventsourcing/examples/bankaccounts/test.py +173 -0
  59. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  60. eventsourcing/examples/cargoshipping/application.py +126 -0
  61. eventsourcing/examples/cargoshipping/domainmodel.py +330 -0
  62. eventsourcing/examples/cargoshipping/interface.py +143 -0
  63. eventsourcing/examples/cargoshipping/test.py +231 -0
  64. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  65. eventsourcing/examples/contentmanagement/application.py +118 -0
  66. eventsourcing/examples/contentmanagement/domainmodel.py +69 -0
  67. eventsourcing/examples/contentmanagement/test.py +180 -0
  68. eventsourcing/examples/contentmanagement/utils.py +26 -0
  69. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  70. eventsourcing/examples/contentmanagementsystem/application.py +54 -0
  71. eventsourcing/examples/contentmanagementsystem/postgres.py +17 -0
  72. eventsourcing/examples/contentmanagementsystem/sqlite.py +17 -0
  73. eventsourcing/examples/contentmanagementsystem/system.py +14 -0
  74. eventsourcing/examples/contentmanagementsystem/test_system.py +180 -0
  75. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  76. eventsourcing/examples/searchablecontent/application.py +45 -0
  77. eventsourcing/examples/searchablecontent/persistence.py +23 -0
  78. eventsourcing/examples/searchablecontent/postgres.py +118 -0
  79. eventsourcing/examples/searchablecontent/sqlite.py +136 -0
  80. eventsourcing/examples/searchablecontent/test_application.py +110 -0
  81. eventsourcing/examples/searchablecontent/test_recorder.py +68 -0
  82. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  83. eventsourcing/examples/searchabletimestamps/application.py +32 -0
  84. eventsourcing/examples/searchabletimestamps/persistence.py +20 -0
  85. eventsourcing/examples/searchabletimestamps/postgres.py +110 -0
  86. eventsourcing/examples/searchabletimestamps/sqlite.py +99 -0
  87. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +94 -0
  88. eventsourcing/examples/test_invoice.py +176 -0
  89. eventsourcing/examples/test_parking_lot.py +206 -0
  90. eventsourcing/interface.py +4 -2
  91. eventsourcing/persistence.py +88 -82
  92. eventsourcing/popo.py +32 -31
  93. eventsourcing/postgres.py +388 -593
  94. eventsourcing/sqlite.py +100 -102
  95. eventsourcing/system.py +66 -71
  96. eventsourcing/tests/application.py +20 -32
  97. eventsourcing/tests/application_tests/__init__.py +0 -0
  98. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +55 -0
  99. eventsourcing/tests/application_tests/test_application_with_popo.py +22 -0
  100. eventsourcing/tests/application_tests/test_application_with_postgres.py +75 -0
  101. eventsourcing/tests/application_tests/test_application_with_sqlite.py +72 -0
  102. eventsourcing/tests/application_tests/test_cache.py +134 -0
  103. eventsourcing/tests/application_tests/test_event_sourced_log.py +162 -0
  104. eventsourcing/tests/application_tests/test_notificationlog.py +232 -0
  105. eventsourcing/tests/application_tests/test_notificationlogreader.py +126 -0
  106. eventsourcing/tests/application_tests/test_processapplication.py +110 -0
  107. eventsourcing/tests/application_tests/test_processingpolicy.py +109 -0
  108. eventsourcing/tests/application_tests/test_repository.py +504 -0
  109. eventsourcing/tests/application_tests/test_snapshotting.py +68 -0
  110. eventsourcing/tests/application_tests/test_upcasting.py +459 -0
  111. eventsourcing/tests/docs_tests/__init__.py +0 -0
  112. eventsourcing/tests/docs_tests/test_docs.py +293 -0
  113. eventsourcing/tests/domain.py +1 -1
  114. eventsourcing/tests/domain_tests/__init__.py +0 -0
  115. eventsourcing/tests/domain_tests/test_aggregate.py +1180 -0
  116. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +1604 -0
  117. eventsourcing/tests/domain_tests/test_domainevent.py +80 -0
  118. eventsourcing/tests/interface_tests/__init__.py +0 -0
  119. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +258 -0
  120. eventsourcing/tests/persistence.py +52 -50
  121. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  122. eventsourcing/tests/persistence_tests/test_aes.py +93 -0
  123. eventsourcing/tests/persistence_tests/test_connection_pool.py +722 -0
  124. eventsourcing/tests/persistence_tests/test_eventstore.py +72 -0
  125. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +21 -0
  126. eventsourcing/tests/persistence_tests/test_mapper.py +113 -0
  127. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +69 -0
  128. eventsourcing/tests/persistence_tests/test_popo.py +124 -0
  129. eventsourcing/tests/persistence_tests/test_postgres.py +1119 -0
  130. eventsourcing/tests/persistence_tests/test_sqlite.py +348 -0
  131. eventsourcing/tests/persistence_tests/test_transcoder.py +44 -0
  132. eventsourcing/tests/postgres_utils.py +7 -7
  133. eventsourcing/tests/system_tests/__init__.py +0 -0
  134. eventsourcing/tests/system_tests/test_runner.py +935 -0
  135. eventsourcing/tests/system_tests/test_system.py +284 -0
  136. eventsourcing/tests/utils_tests/__init__.py +0 -0
  137. eventsourcing/tests/utils_tests/test_utils.py +226 -0
  138. eventsourcing/utils.py +49 -50
  139. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/METADATA +30 -33
  140. eventsourcing-9.3.0.dist-info/RECORD +145 -0
  141. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/WHEEL +1 -2
  142. eventsourcing-9.2.21.dist-info/RECORD +0 -25
  143. eventsourcing-9.2.21.dist-info/top_level.txt +0 -1
  144. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/AUTHORS +0 -0
  145. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,72 @@
1
+ import os
2
+ from unittest import TestCase
3
+
4
+ from eventsourcing.tests.application import (
5
+ TIMEIT_FACTOR,
6
+ ApplicationTestCase,
7
+ ExampleApplicationTestCase,
8
+ )
9
+ from eventsourcing.tests.persistence import tmpfile_uris
10
+
11
+
12
+ class WithSQLiteFile(TestCase):
13
+ timeit_number = 30 * TIMEIT_FACTOR
14
+ expected_factory_topic = "eventsourcing.sqlite:Factory"
15
+
16
+ def setUp(self) -> None:
17
+ super().setUp()
18
+ self.uris = tmpfile_uris()
19
+ # self.db_uri = next(self.uris)
20
+
21
+ os.environ["PERSISTENCE_MODULE"] = "eventsourcing.sqlite"
22
+ os.environ["CREATE_TABLE"] = "y"
23
+ os.environ["SQLITE_DBNAME"] = next(self.uris)
24
+
25
+ def tearDown(self) -> None:
26
+ del os.environ["PERSISTENCE_MODULE"]
27
+ del os.environ["CREATE_TABLE"]
28
+ del os.environ["SQLITE_DBNAME"]
29
+ super().tearDown()
30
+
31
+
32
+ class WithSQLiteInMemory(TestCase):
33
+ timeit_number = 30 * TIMEIT_FACTOR
34
+ expected_factory_topic = "eventsourcing.sqlite:Factory"
35
+
36
+ def setUp(self) -> None:
37
+ super().setUp()
38
+ os.environ["PERSISTENCE_MODULE"] = "eventsourcing.sqlite"
39
+ os.environ["CREATE_TABLE"] = "y"
40
+ os.environ["SQLITE_DBNAME"] = "file:memory:?mode=memory&cache=shared"
41
+
42
+ def tearDown(self) -> None:
43
+ del os.environ["PERSISTENCE_MODULE"]
44
+ del os.environ["CREATE_TABLE"]
45
+ del os.environ["SQLITE_DBNAME"]
46
+ super().tearDown()
47
+
48
+
49
+ class TestApplicationWithSQLiteFile(ApplicationTestCase, WithSQLiteFile):
50
+ pass
51
+
52
+
53
+ # class TestApplicationWithSQLiteInMemory(TestApplication, WithSQLiteInMemory):
54
+ # pass
55
+
56
+
57
+ class TestExampleApplicationWithSQLiteFile(ExampleApplicationTestCase, WithSQLiteFile):
58
+ timeit_number = 30 * TIMEIT_FACTOR
59
+ expected_factory_topic = "eventsourcing.sqlite:Factory"
60
+
61
+
62
+ class TestExampleApplicationWithSQLiteInMemory(
63
+ ExampleApplicationTestCase, WithSQLiteInMemory
64
+ ):
65
+ timeit_number = 30 * TIMEIT_FACTOR
66
+ expected_factory_topic = "eventsourcing.sqlite:Factory"
67
+
68
+
69
+ del ApplicationTestCase
70
+ del ExampleApplicationTestCase
71
+ del WithSQLiteFile
72
+ del WithSQLiteInMemory
@@ -0,0 +1,134 @@
1
+ from unittest import TestCase
2
+
3
+ from eventsourcing.application import Cache, LRUCache
4
+
5
+
6
+ class TestCache(TestCase):
7
+ def test_put_get(self):
8
+ cache = Cache()
9
+
10
+ with self.assertRaises(KeyError):
11
+ cache.get(1)
12
+
13
+ cache.put(1, 1)
14
+ self.assertEqual(cache.get(1), 1)
15
+
16
+ cache.put(2, 2)
17
+ self.assertEqual(1, cache.get(1))
18
+ self.assertEqual(2, cache.get(2))
19
+
20
+ cache.put(3, 3)
21
+ self.assertEqual(3, cache.get(3))
22
+ self.assertEqual(2, cache.get(2))
23
+ self.assertEqual(1, cache.get(1))
24
+
25
+ cache.put(1, 2)
26
+ self.assertEqual(2, cache.get(1))
27
+ cache.put(1, 3)
28
+ self.assertEqual(3, cache.get(1))
29
+
30
+ cache.get(1, evict=True)
31
+ with self.assertRaises(KeyError):
32
+ cache.get(1)
33
+
34
+ cache.put(1, None)
35
+ with self.assertRaises(KeyError):
36
+ cache.get(1)
37
+
38
+
39
+ class TestLRUCache(TestCase):
40
+ def test_put_get(self):
41
+ cache = LRUCache(maxsize=2)
42
+
43
+ with self.assertRaises(KeyError):
44
+ cache.get(1)
45
+
46
+ evicted = cache.put(1, 1)
47
+ self.assertEqual(evicted, (None, None))
48
+ self.assertEqual(cache.get(1), 1)
49
+
50
+ evicted = cache.put(2, 2)
51
+ self.assertEqual(evicted, (None, None))
52
+ self.assertEqual(1, cache.get(1))
53
+ self.assertEqual(2, cache.get(2))
54
+
55
+ evicted = cache.put(3, 3)
56
+ self.assertEqual(evicted, (1, 1))
57
+
58
+ self.assertEqual(3, cache.get(3))
59
+ self.assertEqual(2, cache.get(2))
60
+
61
+ cache.put(1, 1)
62
+ self.assertEqual(1, cache.get(1))
63
+ cache.put(1, 2)
64
+ self.assertEqual(2, cache.get(1))
65
+ cache.put(1, 3)
66
+ self.assertEqual(3, cache.get(1))
67
+
68
+ def test_put_get_evict_recent(self):
69
+ cache = LRUCache(maxsize=3)
70
+
71
+ cache.put(1, 1)
72
+ self.assertEqual(1, cache.get(1))
73
+ self.assertEqual(1, cache.get(1))
74
+ self.assertEqual(1, cache.get(1, evict=True))
75
+
76
+ with self.assertRaises(KeyError):
77
+ self.assertEqual(1, cache.get(1))
78
+
79
+ cache.put(1, 1)
80
+ cache.put(2, 2)
81
+ cache.put(3, 3)
82
+ evicted = cache.put(4, 4)
83
+ self.assertEqual(evicted, (1, 1))
84
+
85
+ self.assertEqual(3, cache.get(3, evict=True))
86
+
87
+ evicted = cache.put(5, 5)
88
+ self.assertEqual(evicted, (None, None))
89
+
90
+ evicted = cache.put(6, 6)
91
+ self.assertEqual(evicted, (2, 2))
92
+
93
+ evicted = cache.put(7, 7)
94
+ self.assertEqual(evicted, (4, 4))
95
+
96
+ def test_put_get_evict_oldest(self):
97
+ cache = LRUCache(maxsize=3)
98
+
99
+ cache.put(1, 1)
100
+ cache.put(2, 2)
101
+ cache.put(3, 3)
102
+ self.assertEqual(1, cache.get(1, evict=True))
103
+
104
+ evicted = cache.put(4, 4)
105
+ self.assertEqual(evicted, (None, None))
106
+
107
+ evicted = cache.put(5, 5)
108
+ self.assertEqual(evicted, (2, 2))
109
+
110
+ evicted = cache.put(6, 6)
111
+ self.assertEqual(evicted, (3, 3))
112
+
113
+ evicted = cache.put(7, 7)
114
+ self.assertEqual(evicted, (4, 4))
115
+
116
+ def test_put_get_evict_newest(self):
117
+ cache = LRUCache(maxsize=3)
118
+
119
+ cache.put(1, 1)
120
+ cache.put(2, 2)
121
+ cache.put(3, 3)
122
+ self.assertEqual(3, cache.get(3, evict=True))
123
+
124
+ evicted = cache.put(4, 4)
125
+ self.assertEqual(evicted, (None, None))
126
+
127
+ evicted = cache.put(5, 5)
128
+ self.assertEqual(evicted, (1, 1))
129
+
130
+ evicted = cache.put(6, 6)
131
+ self.assertEqual(evicted, (2, 2))
132
+
133
+ evicted = cache.put(7, 7)
134
+ self.assertEqual(evicted, (4, 4))
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+ from unittest import TestCase
5
+ from uuid import NAMESPACE_URL, UUID, uuid4, uuid5
6
+
7
+ from eventsourcing.application import Application, EventSourcedLog
8
+ from eventsourcing.domain import Aggregate, DomainEvent
9
+ from eventsourcing.persistence import (
10
+ DatetimeAsISO,
11
+ DecimalAsStr,
12
+ EventStore,
13
+ JSONTranscoder,
14
+ Mapper,
15
+ UUIDAsHex,
16
+ )
17
+ from eventsourcing.popo import POPOAggregateRecorder
18
+
19
+ if TYPE_CHECKING: # pragma: nocover
20
+ from eventsourcing.utils import EnvType
21
+
22
+
23
+ class TestEventSourcedLog(TestCase):
24
+ def test_logging_aggregate_ids(self) -> None:
25
+ class LoggedID(DomainEvent):
26
+ aggregate_id: UUID
27
+
28
+ transcoder = JSONTranscoder()
29
+ transcoder.register(UUIDAsHex())
30
+ transcoder.register(DecimalAsStr())
31
+ transcoder.register(DatetimeAsISO())
32
+
33
+ event_recorder = POPOAggregateRecorder()
34
+ event_store = EventStore(
35
+ mapper=Mapper(transcoder=transcoder),
36
+ recorder=event_recorder,
37
+ )
38
+
39
+ log: EventSourcedLog[LoggedID] = EventSourcedLog(
40
+ events=event_store,
41
+ originator_id=uuid5(NAMESPACE_URL, "/aggregates"),
42
+ logged_cls=LoggedID,
43
+ )
44
+ id1 = uuid4()
45
+ id2 = uuid4()
46
+ id3 = uuid4()
47
+
48
+ self.assertEqual(log.get_first(), None)
49
+ self.assertEqual(log.get_last(), None)
50
+ logged = log.trigger_event(aggregate_id=id1)
51
+ event_store.put([logged])
52
+ first = log.get_first()
53
+ assert first
54
+ self.assertEqual(first.aggregate_id, id1)
55
+ last = log.get_last()
56
+ assert last
57
+ self.assertEqual(last.aggregate_id, id1)
58
+ logged = log.trigger_event(aggregate_id=id2)
59
+ event_store.put([logged])
60
+ last = log.get_last()
61
+ assert last
62
+ self.assertEqual(last.aggregate_id, id2)
63
+ logged = log.trigger_event(aggregate_id=id3, next_originator_version=3)
64
+ event_store.put([logged])
65
+ last = log.get_last()
66
+ assert last
67
+ self.assertEqual(last.aggregate_id, id3)
68
+ first = log.get_first()
69
+ assert first
70
+ self.assertEqual(first.aggregate_id, id1)
71
+
72
+ ids = [e.aggregate_id for e in log.get()]
73
+ self.assertEqual(ids, [id1, id2, id3])
74
+
75
+ ids = [e.aggregate_id for e in log.get(gt=1)]
76
+ self.assertEqual(ids, [id2, id3])
77
+
78
+ ids = [e.aggregate_id for e in log.get(lte=2)]
79
+ self.assertEqual(ids, [id1, id2])
80
+
81
+ ids = [e.aggregate_id for e in log.get(limit=1)]
82
+ self.assertEqual(ids, [id1])
83
+
84
+ ids = [e.aggregate_id for e in log.get(desc=True)]
85
+ self.assertEqual(ids, [id3, id2, id1])
86
+
87
+ def test_with_application(self) -> None:
88
+ class LoggedID(DomainEvent):
89
+ aggregate_id: UUID
90
+
91
+ class MyApplication(Application):
92
+ def __init__(self, env: EnvType | None = None) -> None:
93
+ super().__init__(env=env)
94
+ self.aggregate_log = EventSourcedLog(
95
+ events=self.events,
96
+ originator_id=uuid5(NAMESPACE_URL, "/aggregates"),
97
+ logged_cls=LoggedID,
98
+ )
99
+
100
+ def create_aggregate(self) -> UUID:
101
+ aggregate = Aggregate()
102
+ logged_id = self.aggregate_log.trigger_event(aggregate_id=aggregate.id)
103
+ self.save(aggregate, logged_id)
104
+ return aggregate.id
105
+
106
+ app = MyApplication()
107
+
108
+ self.assertEqual(app.aggregate_log.get_last(), None)
109
+
110
+ aggregate1_id = app.create_aggregate()
111
+ last = app.aggregate_log.get_last()
112
+ assert last
113
+ self.assertEqual(last.aggregate_id, aggregate1_id)
114
+
115
+ aggregate2_id = app.create_aggregate()
116
+ last = app.aggregate_log.get_last()
117
+ assert last
118
+ self.assertEqual(last.aggregate_id, aggregate2_id)
119
+
120
+ aggregate_ids = [i.aggregate_id for i in app.aggregate_log.get()]
121
+ self.assertEqual(aggregate_ids, [aggregate1_id, aggregate2_id])
122
+
123
+ def test_subclasses(self) -> None:
124
+ transcoder = JSONTranscoder()
125
+ transcoder.register(UUIDAsHex())
126
+ transcoder.register(DecimalAsStr())
127
+ transcoder.register(DatetimeAsISO())
128
+
129
+ event_recorder = POPOAggregateRecorder()
130
+ event_store = EventStore(
131
+ mapper=Mapper(transcoder=transcoder),
132
+ recorder=event_recorder,
133
+ )
134
+
135
+ class TransactionLogEvent(DomainEvent):
136
+ pass
137
+
138
+ class AccountCredited(TransactionLogEvent):
139
+ pass
140
+
141
+ class AccountDebited(TransactionLogEvent):
142
+ pass
143
+
144
+ # Subclass EventSourcedLog.
145
+ class TransactionLog(EventSourcedLog[TransactionLogEvent]):
146
+ def account_credited(self) -> AccountCredited:
147
+ return self._trigger_event(logged_cls=AccountCredited)
148
+
149
+ def account_debited(self) -> AccountDebited:
150
+ return self._trigger_event(logged_cls=AccountDebited)
151
+
152
+ transaction_log = TransactionLog(
153
+ events=event_store,
154
+ originator_id=uuid5(NAMESPACE_URL, "/aggregates"),
155
+ logged_cls=TransactionLogEvent,
156
+ )
157
+
158
+ account_credited = transaction_log.account_credited()
159
+ self.assertIsInstance(account_credited, AccountCredited)
160
+
161
+ account_debited = transaction_log.account_debited()
162
+ self.assertIsInstance(account_debited, AccountDebited)
@@ -0,0 +1,232 @@
1
+ from unittest.case import TestCase
2
+ from uuid import uuid4
3
+
4
+ from eventsourcing.application import LocalNotificationLog
5
+ from eventsourcing.persistence import StoredEvent
6
+ from eventsourcing.sqlite import SQLiteApplicationRecorder, SQLiteDatastore
7
+
8
+
9
+ class TestNotificationLog(TestCase):
10
+ def test_get_section(self):
11
+ recorder = SQLiteApplicationRecorder(SQLiteDatastore(":memory:"))
12
+ recorder.create_table()
13
+
14
+ # Construct notification log.
15
+ notification_log = LocalNotificationLog(recorder, section_size=5)
16
+
17
+ # Get the "current" section of log.
18
+ section = notification_log["1,10"]
19
+ self.assertEqual(len(section.items), 0) # event notifications
20
+ self.assertEqual(section.id, None)
21
+ self.assertEqual(section.next_id, None)
22
+
23
+ # Write 5 events.
24
+ originator_id = uuid4()
25
+ for i in range(5):
26
+ stored_event = StoredEvent(
27
+ originator_id=originator_id,
28
+ originator_version=i,
29
+ topic="topic",
30
+ state=b"state",
31
+ )
32
+ recorder.insert_events([stored_event])
33
+
34
+ # Get the "head" section of log.
35
+ section = notification_log["1,10"]
36
+ self.assertEqual(len(section.items), 5) # event notifications
37
+ self.assertEqual(section.items[0].id, 1)
38
+ self.assertEqual(section.items[1].id, 2)
39
+ self.assertEqual(section.items[2].id, 3)
40
+ self.assertEqual(section.items[3].id, 4)
41
+ self.assertEqual(section.items[4].id, 5)
42
+ self.assertEqual(section.id, "1,5")
43
+ self.assertEqual(section.next_id, "6,10")
44
+
45
+ # Get the "1,5" section of log.
46
+ section = notification_log["1,5"]
47
+ self.assertEqual(len(section.items), 5) # event notifications
48
+ self.assertEqual(section.items[0].id, 1)
49
+ self.assertEqual(section.items[1].id, 2)
50
+ self.assertEqual(section.items[2].id, 3)
51
+ self.assertEqual(section.items[3].id, 4)
52
+ self.assertEqual(section.items[4].id, 5)
53
+ self.assertEqual(section.id, "1,5")
54
+ self.assertEqual(section.next_id, "6,10")
55
+
56
+ # Get the next section of log.
57
+ section = notification_log["6,10"]
58
+ self.assertEqual(len(section.items), 0) # event notifications
59
+ self.assertEqual(section.id, None)
60
+ self.assertEqual(section.next_id, None)
61
+
62
+ # Write 4 events.
63
+ originator_id = uuid4()
64
+ for i in range(4):
65
+ stored_event = StoredEvent(
66
+ originator_id=originator_id,
67
+ originator_version=i,
68
+ topic="topic",
69
+ state=b"state",
70
+ )
71
+ recorder.insert_events([stored_event])
72
+
73
+ # Get the next section of log.
74
+ section = notification_log["6,10"]
75
+ self.assertEqual(len(section.items), 4) # event notifications
76
+ self.assertEqual(section.items[0].id, 6)
77
+ self.assertEqual(section.items[1].id, 7)
78
+ self.assertEqual(section.items[2].id, 8)
79
+ self.assertEqual(section.items[3].id, 9)
80
+ self.assertEqual(section.id, "6,9")
81
+ self.assertEqual(section.next_id, None)
82
+
83
+ # Start at non-regular section start.
84
+ section = notification_log["3,7"]
85
+ self.assertEqual(len(section.items), 5) # event notifications
86
+ self.assertEqual(section.items[0].id, 3)
87
+ self.assertEqual(section.items[1].id, 4)
88
+ self.assertEqual(section.items[2].id, 5)
89
+ self.assertEqual(section.items[3].id, 6)
90
+ self.assertEqual(section.items[4].id, 7)
91
+ self.assertEqual(section.id, "3,7")
92
+ self.assertEqual(section.next_id, "8,12")
93
+
94
+ # Notification log limits section size.
95
+ section = notification_log["3,10"]
96
+ self.assertEqual(len(section.items), 5) # event notifications
97
+ self.assertEqual(section.items[0].id, 3)
98
+ self.assertEqual(section.items[1].id, 4)
99
+ self.assertEqual(section.items[2].id, 5)
100
+ self.assertEqual(section.items[3].id, 6)
101
+ self.assertEqual(section.items[4].id, 7)
102
+ self.assertEqual(section.id, "3,7")
103
+ self.assertEqual(section.next_id, "8,12")
104
+
105
+ # Reader limits section size.
106
+ section = notification_log["3,4"]
107
+ self.assertEqual(len(section.items), 2) # event notifications
108
+ self.assertEqual(section.items[0].id, 3)
109
+ self.assertEqual(section.items[1].id, 4)
110
+ self.assertEqual(section.id, "3,4")
111
+ self.assertEqual(section.next_id, "5,6")
112
+
113
+ # Meaningless section ID.
114
+ section = notification_log["3,2"]
115
+ self.assertEqual(len(section.items), 0) # event notifications
116
+ self.assertEqual(section.id, None)
117
+ self.assertEqual(section.next_id, None)
118
+
119
+ # # Numbers below 1.
120
+ # section = notification_log["-3,0"]
121
+ # self.assertEqual(
122
+ # len(section.items), 4
123
+ # ) # event notifications
124
+ # self.assertEqual(section.items[0].id, 6)
125
+ # self.assertEqual(section.items[1].id, 7)
126
+ # self.assertEqual(section.items[2].id, 8)
127
+ # self.assertEqual(section.items[3].id, 9)
128
+ # self.assertEqual(section.section_id, "6,9")
129
+ # self.assertEqual(section.next_id, None)
130
+ #
131
+ # section = notification_log["-4,0"]
132
+ # self.assertEqual(
133
+ # len(section.items), 5
134
+ # ) # event notifications
135
+ # self.assertEqual(section.items[0].id, 5)
136
+ # self.assertEqual(section.items[1].id, 6)
137
+ # self.assertEqual(section.items[2].id, 7)
138
+ # self.assertEqual(section.items[3].id, 8)
139
+ # self.assertEqual(section.items[4].id, 9)
140
+ # self.assertEqual(section.section_id, "5,9")
141
+ # self.assertEqual(section.next_id, "10,14")
142
+ #
143
+ # section = notification_log["-4,-1"]
144
+ # self.assertEqual(
145
+ # len(section.items), 4
146
+ # ) # event notifications
147
+ # self.assertEqual(section.items[0].id, 5)
148
+ # self.assertEqual(section.items[1].id, 6)
149
+ # self.assertEqual(section.items[2].id, 7)
150
+ # self.assertEqual(section.items[3].id, 8)
151
+ # self.assertEqual(section.section_id, "5,8")
152
+ # self.assertEqual(section.next_id, "9,12")
153
+
154
+ def test_select(self):
155
+ recorder = SQLiteApplicationRecorder(SQLiteDatastore(":memory:"))
156
+ recorder.create_table()
157
+
158
+ # Construct notification log.
159
+ notification_log = LocalNotificationLog(recorder, section_size=10)
160
+
161
+ # Select start 1, limit 10
162
+ notifications = notification_log.select(1, 10)
163
+ self.assertEqual(len(notifications), 0)
164
+
165
+ # Write 5 events.
166
+ originator_id = uuid4()
167
+ for i in range(5):
168
+ stored_event = StoredEvent(
169
+ originator_id=originator_id,
170
+ originator_version=i,
171
+ topic="topic",
172
+ state=b"state",
173
+ )
174
+ recorder.insert_events([stored_event])
175
+
176
+ # Select start 1, limit 10
177
+ notifications = notification_log.select(1, 5)
178
+ self.assertEqual(len(notifications), 5)
179
+ self.assertEqual(notifications[0].id, 1)
180
+ self.assertEqual(notifications[1].id, 2)
181
+ self.assertEqual(notifications[2].id, 3)
182
+ self.assertEqual(notifications[3].id, 4)
183
+ self.assertEqual(notifications[4].id, 5)
184
+
185
+ # Select start 1, limit 10
186
+ notifications = notification_log.select(1, 5)
187
+ self.assertEqual(len(notifications), 5)
188
+ self.assertEqual(notifications[0].id, 1)
189
+ self.assertEqual(notifications[1].id, 2)
190
+ self.assertEqual(notifications[2].id, 3)
191
+ self.assertEqual(notifications[3].id, 4)
192
+ self.assertEqual(notifications[4].id, 5)
193
+
194
+ # Select start 6, limit 5
195
+ notifications = notification_log.select(6, 5)
196
+ self.assertEqual(len(notifications), 0)
197
+
198
+ # Write 4 events.
199
+ originator_id = uuid4()
200
+ for i in range(4):
201
+ stored_event = StoredEvent(
202
+ originator_id=originator_id,
203
+ originator_version=i,
204
+ topic="topic",
205
+ state=b"state",
206
+ )
207
+ recorder.insert_events([stored_event])
208
+
209
+ # Select start 6, limit 5
210
+ notifications = notification_log.select(6, 5)
211
+ self.assertEqual(len(notifications), 4) # event notifications
212
+ self.assertEqual(notifications[0].id, 6)
213
+ self.assertEqual(notifications[1].id, 7)
214
+ self.assertEqual(notifications[2].id, 8)
215
+ self.assertEqual(notifications[3].id, 9)
216
+
217
+ # Select start 3, limit 5
218
+ notifications = notification_log.select(3, 5)
219
+ self.assertEqual(len(notifications), 5) # event notifications
220
+ self.assertEqual(notifications[0].id, 3)
221
+ self.assertEqual(notifications[1].id, 4)
222
+ self.assertEqual(notifications[2].id, 5)
223
+ self.assertEqual(notifications[3].id, 6)
224
+ self.assertEqual(notifications[4].id, 7)
225
+
226
+ # Notification log limits limit.
227
+ # Select start 1, limit 20
228
+ with self.assertRaises(ValueError) as cm:
229
+ notification_log.select(1, 20)
230
+ self.assertEqual(
231
+ cm.exception.args[0], "Requested limit 20 greater than section size 10"
232
+ )