eventsourcing 9.3.2__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.
- eventsourcing/postgres.py +2 -1
- eventsourcing/system.py +3 -1
- {eventsourcing-9.3.2.dist-info → eventsourcing-9.3.4.dist-info}/METADATA +3 -8
- eventsourcing-9.3.4.dist-info/RECORD +24 -0
- eventsourcing/examples/__init__.py +0 -0
- eventsourcing/examples/aggregate1/__init__.py +0 -0
- eventsourcing/examples/aggregate1/application.py +0 -27
- eventsourcing/examples/aggregate1/domainmodel.py +0 -16
- eventsourcing/examples/aggregate1/test_application.py +0 -37
- eventsourcing/examples/aggregate2/__init__.py +0 -0
- eventsourcing/examples/aggregate2/application.py +0 -27
- eventsourcing/examples/aggregate2/domainmodel.py +0 -22
- eventsourcing/examples/aggregate2/test_application.py +0 -37
- eventsourcing/examples/aggregate3/__init__.py +0 -0
- eventsourcing/examples/aggregate3/application.py +0 -27
- eventsourcing/examples/aggregate3/domainmodel.py +0 -38
- eventsourcing/examples/aggregate3/test_application.py +0 -37
- eventsourcing/examples/aggregate4/__init__.py +0 -0
- eventsourcing/examples/aggregate4/application.py +0 -27
- eventsourcing/examples/aggregate4/domainmodel.py +0 -114
- eventsourcing/examples/aggregate4/test_application.py +0 -38
- eventsourcing/examples/aggregate5/__init__.py +0 -0
- eventsourcing/examples/aggregate5/application.py +0 -27
- eventsourcing/examples/aggregate5/domainmodel.py +0 -131
- eventsourcing/examples/aggregate5/test_application.py +0 -38
- eventsourcing/examples/aggregate6/__init__.py +0 -0
- eventsourcing/examples/aggregate6/application.py +0 -30
- eventsourcing/examples/aggregate6/domainmodel.py +0 -123
- eventsourcing/examples/aggregate6/test_application.py +0 -38
- eventsourcing/examples/aggregate6a/__init__.py +0 -0
- eventsourcing/examples/aggregate6a/application.py +0 -40
- eventsourcing/examples/aggregate6a/domainmodel.py +0 -149
- eventsourcing/examples/aggregate6a/test_application.py +0 -45
- eventsourcing/examples/aggregate7/__init__.py +0 -0
- eventsourcing/examples/aggregate7/application.py +0 -48
- eventsourcing/examples/aggregate7/domainmodel.py +0 -144
- eventsourcing/examples/aggregate7/persistence.py +0 -57
- eventsourcing/examples/aggregate7/test_application.py +0 -38
- eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -45
- eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -67
- eventsourcing/examples/aggregate7a/__init__.py +0 -0
- eventsourcing/examples/aggregate7a/application.py +0 -56
- eventsourcing/examples/aggregate7a/domainmodel.py +0 -170
- eventsourcing/examples/aggregate7a/test_application.py +0 -46
- eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -45
- eventsourcing/examples/aggregate8/__init__.py +0 -0
- eventsourcing/examples/aggregate8/application.py +0 -47
- eventsourcing/examples/aggregate8/domainmodel.py +0 -65
- eventsourcing/examples/aggregate8/persistence.py +0 -57
- eventsourcing/examples/aggregate8/test_application.py +0 -37
- eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -44
- eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -38
- eventsourcing/examples/bankaccounts/__init__.py +0 -0
- eventsourcing/examples/bankaccounts/application.py +0 -70
- eventsourcing/examples/bankaccounts/domainmodel.py +0 -56
- eventsourcing/examples/bankaccounts/test.py +0 -173
- eventsourcing/examples/cargoshipping/__init__.py +0 -0
- eventsourcing/examples/cargoshipping/application.py +0 -126
- eventsourcing/examples/cargoshipping/domainmodel.py +0 -330
- eventsourcing/examples/cargoshipping/interface.py +0 -143
- eventsourcing/examples/cargoshipping/test.py +0 -231
- eventsourcing/examples/contentmanagement/__init__.py +0 -0
- eventsourcing/examples/contentmanagement/application.py +0 -118
- eventsourcing/examples/contentmanagement/domainmodel.py +0 -69
- eventsourcing/examples/contentmanagement/test.py +0 -180
- eventsourcing/examples/contentmanagement/utils.py +0 -26
- eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
- eventsourcing/examples/contentmanagementsystem/application.py +0 -54
- eventsourcing/examples/contentmanagementsystem/postgres.py +0 -17
- eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -17
- eventsourcing/examples/contentmanagementsystem/system.py +0 -14
- eventsourcing/examples/contentmanagementsystem/test_system.py +0 -180
- eventsourcing/examples/searchablecontent/__init__.py +0 -0
- eventsourcing/examples/searchablecontent/application.py +0 -45
- eventsourcing/examples/searchablecontent/persistence.py +0 -23
- eventsourcing/examples/searchablecontent/postgres.py +0 -118
- eventsourcing/examples/searchablecontent/sqlite.py +0 -136
- eventsourcing/examples/searchablecontent/test_application.py +0 -110
- eventsourcing/examples/searchablecontent/test_recorder.py +0 -68
- eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
- eventsourcing/examples/searchabletimestamps/application.py +0 -32
- eventsourcing/examples/searchabletimestamps/persistence.py +0 -20
- eventsourcing/examples/searchabletimestamps/postgres.py +0 -110
- eventsourcing/examples/searchabletimestamps/sqlite.py +0 -99
- eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +0 -94
- eventsourcing/examples/test_invoice.py +0 -176
- eventsourcing/examples/test_parking_lot.py +0 -206
- eventsourcing/tests/application_tests/__init__.py +0 -0
- eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -55
- eventsourcing/tests/application_tests/test_application_with_popo.py +0 -22
- eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -75
- eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -72
- eventsourcing/tests/application_tests/test_cache.py +0 -134
- eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -162
- eventsourcing/tests/application_tests/test_notificationlog.py +0 -232
- eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -126
- eventsourcing/tests/application_tests/test_processapplication.py +0 -110
- eventsourcing/tests/application_tests/test_processingpolicy.py +0 -109
- eventsourcing/tests/application_tests/test_repository.py +0 -504
- eventsourcing/tests/application_tests/test_snapshotting.py +0 -68
- eventsourcing/tests/application_tests/test_upcasting.py +0 -459
- eventsourcing/tests/docs_tests/__init__.py +0 -0
- eventsourcing/tests/docs_tests/test_docs.py +0 -293
- eventsourcing/tests/domain_tests/__init__.py +0 -0
- eventsourcing/tests/domain_tests/test_aggregate.py +0 -1200
- eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -1604
- eventsourcing/tests/domain_tests/test_domainevent.py +0 -80
- eventsourcing/tests/interface_tests/__init__.py +0 -0
- eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -258
- eventsourcing/tests/persistence_tests/__init__.py +0 -0
- eventsourcing/tests/persistence_tests/test_aes.py +0 -93
- eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -722
- eventsourcing/tests/persistence_tests/test_eventstore.py +0 -72
- eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -21
- eventsourcing/tests/persistence_tests/test_mapper.py +0 -113
- eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -69
- eventsourcing/tests/persistence_tests/test_popo.py +0 -124
- eventsourcing/tests/persistence_tests/test_postgres.py +0 -1119
- eventsourcing/tests/persistence_tests/test_sqlite.py +0 -348
- eventsourcing/tests/persistence_tests/test_transcoder.py +0 -44
- eventsourcing/tests/system_tests/__init__.py +0 -0
- eventsourcing/tests/system_tests/test_runner.py +0 -935
- eventsourcing/tests/system_tests/test_system.py +0 -284
- eventsourcing/tests/utils_tests/__init__.py +0 -0
- eventsourcing/tests/utils_tests/test_utils.py +0 -226
- eventsourcing-9.3.2.dist-info/RECORD +0 -145
- {eventsourcing-9.3.2.dist-info → eventsourcing-9.3.4.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.3.2.dist-info → eventsourcing-9.3.4.dist-info}/LICENSE +0 -0
- {eventsourcing-9.3.2.dist-info → eventsourcing-9.3.4.dist-info}/WHEEL +0 -0
|
@@ -1,1604 +0,0 @@
|
|
|
1
|
-
import contextlib
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from datetime import datetime, timezone
|
|
4
|
-
from unittest import TestCase
|
|
5
|
-
|
|
6
|
-
from eventsourcing.application import Application
|
|
7
|
-
from eventsourcing.domain import (
|
|
8
|
-
Aggregate,
|
|
9
|
-
AggregateCreated,
|
|
10
|
-
AggregateEvent,
|
|
11
|
-
aggregate,
|
|
12
|
-
event,
|
|
13
|
-
triggers,
|
|
14
|
-
)
|
|
15
|
-
from eventsourcing.utils import get_method_name
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class TestAggregateDecorator(TestCase):
|
|
19
|
-
def test_decorate_class_with_no_bases(self):
|
|
20
|
-
@aggregate
|
|
21
|
-
class MyAgg:
|
|
22
|
-
"""My doc"""
|
|
23
|
-
|
|
24
|
-
a: int
|
|
25
|
-
|
|
26
|
-
self.assertTrue(issubclass(MyAgg, Aggregate))
|
|
27
|
-
self.assertTrue(issubclass(MyAgg, MyAgg))
|
|
28
|
-
self.assertTrue(MyAgg.__name__, "MyAgg")
|
|
29
|
-
self.assertTrue(MyAgg.__doc__, "My doc")
|
|
30
|
-
self.assertEqual(MyAgg.__bases__, (Aggregate,))
|
|
31
|
-
self.assertEqual(MyAgg.__annotations__, {"a": int})
|
|
32
|
-
|
|
33
|
-
agg = MyAgg(a=1)
|
|
34
|
-
self.assertEqual(agg.a, 1)
|
|
35
|
-
self.assertEqual(len(agg.pending_events), 1)
|
|
36
|
-
self.assertIsInstance(agg, Aggregate)
|
|
37
|
-
self.assertIsInstance(agg, MyAgg)
|
|
38
|
-
|
|
39
|
-
def test_decorate_class_with_one_base(self):
|
|
40
|
-
class MyBase:
|
|
41
|
-
"My base doc"
|
|
42
|
-
|
|
43
|
-
@aggregate
|
|
44
|
-
class MyAgg(MyBase):
|
|
45
|
-
"""My doc"""
|
|
46
|
-
|
|
47
|
-
a: int
|
|
48
|
-
|
|
49
|
-
self.assertTrue(issubclass(MyAgg, Aggregate))
|
|
50
|
-
self.assertTrue(issubclass(MyAgg, MyAgg))
|
|
51
|
-
self.assertTrue(issubclass(MyAgg, MyBase))
|
|
52
|
-
self.assertTrue(MyAgg.__name__, "MyAgg")
|
|
53
|
-
self.assertTrue(MyAgg.__doc__, "My doc")
|
|
54
|
-
self.assertEqual(MyAgg.__bases__, (MyBase, Aggregate))
|
|
55
|
-
self.assertEqual(MyAgg.__annotations__, {"a": int})
|
|
56
|
-
|
|
57
|
-
agg = MyAgg(a=1)
|
|
58
|
-
self.assertEqual(agg.a, 1)
|
|
59
|
-
self.assertEqual(len(agg.pending_events), 1)
|
|
60
|
-
self.assertIsInstance(agg, Aggregate)
|
|
61
|
-
self.assertIsInstance(agg, MyAgg)
|
|
62
|
-
self.assertIsInstance(agg, MyBase)
|
|
63
|
-
|
|
64
|
-
def test_decorate_class_with_two_bases(self):
|
|
65
|
-
class MyAbstract:
|
|
66
|
-
"My base doc"
|
|
67
|
-
|
|
68
|
-
class MyBase(MyAbstract):
|
|
69
|
-
"My base doc"
|
|
70
|
-
|
|
71
|
-
@aggregate
|
|
72
|
-
class MyAgg(MyBase):
|
|
73
|
-
"""My doc"""
|
|
74
|
-
|
|
75
|
-
a: int
|
|
76
|
-
|
|
77
|
-
self.assertTrue(issubclass(MyAgg, Aggregate))
|
|
78
|
-
self.assertTrue(issubclass(MyAgg, MyAgg))
|
|
79
|
-
self.assertTrue(issubclass(MyAgg, MyBase))
|
|
80
|
-
self.assertTrue(issubclass(MyAgg, MyAbstract))
|
|
81
|
-
self.assertTrue(MyAgg.__name__, "MyAgg")
|
|
82
|
-
self.assertTrue(MyAgg.__doc__, "My doc")
|
|
83
|
-
self.assertEqual(MyAgg.__bases__, (MyBase, Aggregate))
|
|
84
|
-
self.assertEqual(MyAgg.__annotations__, {"a": int})
|
|
85
|
-
|
|
86
|
-
agg = MyAgg(a=1)
|
|
87
|
-
self.assertEqual(agg.a, 1)
|
|
88
|
-
self.assertEqual(len(agg.pending_events), 1)
|
|
89
|
-
self.assertIsInstance(agg, Aggregate)
|
|
90
|
-
self.assertIsInstance(agg, MyAgg)
|
|
91
|
-
self.assertIsInstance(agg, MyBase)
|
|
92
|
-
self.assertIsInstance(agg, MyAbstract)
|
|
93
|
-
|
|
94
|
-
def test_raises_when_decorating_aggregate_subclass(self):
|
|
95
|
-
with self.assertRaises(TypeError) as cm:
|
|
96
|
-
|
|
97
|
-
@aggregate
|
|
98
|
-
class MyAgg(Aggregate):
|
|
99
|
-
pass
|
|
100
|
-
|
|
101
|
-
self.assertIn("MyAgg is already an Aggregate", cm.exception.args[0])
|
|
102
|
-
|
|
103
|
-
def test_aggregate_on_dataclass(self):
|
|
104
|
-
@aggregate
|
|
105
|
-
@dataclass
|
|
106
|
-
class MyAgg:
|
|
107
|
-
value: int
|
|
108
|
-
|
|
109
|
-
a = MyAgg(1)
|
|
110
|
-
self.assertIsInstance(a, MyAgg)
|
|
111
|
-
self.assertEqual(a.value, 1)
|
|
112
|
-
self.assertIsInstance(a, Aggregate)
|
|
113
|
-
self.assertEqual(len(a.pending_events), 1)
|
|
114
|
-
|
|
115
|
-
def test_dataclass_on_aggregate(self):
|
|
116
|
-
@dataclass
|
|
117
|
-
@aggregate
|
|
118
|
-
class MyAgg:
|
|
119
|
-
value: int
|
|
120
|
-
|
|
121
|
-
a = MyAgg(1)
|
|
122
|
-
self.assertIsInstance(a, MyAgg)
|
|
123
|
-
self.assertEqual(a.value, 1)
|
|
124
|
-
self.assertIsInstance(a, Aggregate)
|
|
125
|
-
self.assertEqual(len(a.pending_events), 1)
|
|
126
|
-
|
|
127
|
-
def test_aggregate_decorator_called_with_create_event_name(self):
|
|
128
|
-
@aggregate(created_event_name="Started")
|
|
129
|
-
class MyAgg:
|
|
130
|
-
value: int
|
|
131
|
-
|
|
132
|
-
a = MyAgg(1)
|
|
133
|
-
self.assertIsInstance(a, MyAgg)
|
|
134
|
-
self.assertEqual(a.value, 1)
|
|
135
|
-
self.assertIsInstance(a, Aggregate)
|
|
136
|
-
self.assertEqual(len(a.pending_events), 1)
|
|
137
|
-
self.assertEqual(type(a.pending_events[0]).__name__, "Started")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
class TestEventDecorator(TestCase):
|
|
141
|
-
def test_event_name_inferred_from_method_no_args(self):
|
|
142
|
-
class MyAgg(Aggregate):
|
|
143
|
-
@event
|
|
144
|
-
def heartbeat(self):
|
|
145
|
-
pass
|
|
146
|
-
|
|
147
|
-
a = MyAgg()
|
|
148
|
-
self.assertIsInstance(a, MyAgg)
|
|
149
|
-
a.heartbeat()
|
|
150
|
-
self.assertIsInstance(a, Aggregate)
|
|
151
|
-
self.assertEqual(a.version, 2)
|
|
152
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
153
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.Heartbeat)
|
|
154
|
-
|
|
155
|
-
def test_event_decorator_called_without_args(self):
|
|
156
|
-
class MyAgg(Aggregate):
|
|
157
|
-
@event()
|
|
158
|
-
def heartbeat(self):
|
|
159
|
-
pass
|
|
160
|
-
|
|
161
|
-
a = MyAgg()
|
|
162
|
-
self.assertIsInstance(a, MyAgg)
|
|
163
|
-
a.heartbeat()
|
|
164
|
-
self.assertIsInstance(a, Aggregate)
|
|
165
|
-
self.assertEqual(a.version, 2)
|
|
166
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
167
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.Heartbeat)
|
|
168
|
-
|
|
169
|
-
def test_event_name_inferred_from_method_with_arg(self):
|
|
170
|
-
class MyAgg(Aggregate):
|
|
171
|
-
@event
|
|
172
|
-
def value_changed(self, value):
|
|
173
|
-
self.value = value
|
|
174
|
-
|
|
175
|
-
a = MyAgg()
|
|
176
|
-
self.assertIsInstance(a, MyAgg)
|
|
177
|
-
a.value_changed(1)
|
|
178
|
-
self.assertEqual(a.value, 1)
|
|
179
|
-
self.assertIsInstance(a, Aggregate)
|
|
180
|
-
self.assertEqual(a.version, 2)
|
|
181
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
182
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
183
|
-
|
|
184
|
-
def test_event_name_inferred_from_method_with_kwarg(self):
|
|
185
|
-
class MyAgg(Aggregate):
|
|
186
|
-
@event
|
|
187
|
-
def value_changed(self, value):
|
|
188
|
-
self.value = value
|
|
189
|
-
|
|
190
|
-
a = MyAgg()
|
|
191
|
-
self.assertIsInstance(a, MyAgg)
|
|
192
|
-
a.value_changed(value=1)
|
|
193
|
-
self.assertEqual(a.value, 1)
|
|
194
|
-
self.assertIsInstance(a, Aggregate)
|
|
195
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
196
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
197
|
-
|
|
198
|
-
def test_event_name_inferred_from_method_with_default_kwarg(self):
|
|
199
|
-
class MyAgg(Aggregate):
|
|
200
|
-
@event
|
|
201
|
-
def value_changed(self, value=3):
|
|
202
|
-
self.value = value
|
|
203
|
-
|
|
204
|
-
a = MyAgg()
|
|
205
|
-
self.assertIsInstance(a, MyAgg)
|
|
206
|
-
a.value_changed()
|
|
207
|
-
self.assertEqual(a.value, 3)
|
|
208
|
-
self.assertIsInstance(a, Aggregate)
|
|
209
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
210
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
211
|
-
|
|
212
|
-
def test_method_name_same_on_class_and_instance(self):
|
|
213
|
-
# Check this works with Python object class.
|
|
214
|
-
class MyClass:
|
|
215
|
-
def value_changed(self):
|
|
216
|
-
pass
|
|
217
|
-
|
|
218
|
-
a = MyClass()
|
|
219
|
-
|
|
220
|
-
self.assertEqual(
|
|
221
|
-
get_method_name(a.value_changed), get_method_name(MyClass.value_changed)
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
# Check this works with Aggregate class and @event decorator.
|
|
225
|
-
class MyAggregate(Aggregate):
|
|
226
|
-
@event
|
|
227
|
-
def value_changed(self):
|
|
228
|
-
pass
|
|
229
|
-
|
|
230
|
-
a = MyAggregate()
|
|
231
|
-
|
|
232
|
-
self.assertEqual(
|
|
233
|
-
get_method_name(a.value_changed), get_method_name(MyAggregate.value_changed)
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
self.assertTrue(
|
|
237
|
-
get_method_name(a.value_changed).endswith("value_changed"),
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
self.assertTrue(
|
|
241
|
-
get_method_name(MyAggregate.value_changed).endswith("value_changed"),
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
# Check this works with Aggregate class and @event decorator.
|
|
245
|
-
class MyAggregate2(Aggregate):
|
|
246
|
-
@event()
|
|
247
|
-
def value_changed(self):
|
|
248
|
-
pass
|
|
249
|
-
|
|
250
|
-
a = MyAggregate2()
|
|
251
|
-
|
|
252
|
-
self.assertEqual(
|
|
253
|
-
get_method_name(a.value_changed),
|
|
254
|
-
get_method_name(MyAggregate2.value_changed),
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
self.assertTrue(
|
|
258
|
-
get_method_name(a.value_changed).endswith("value_changed"),
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
self.assertTrue(
|
|
262
|
-
get_method_name(MyAggregate2.value_changed).endswith("value_changed"),
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
def test_raises_when_method_takes_1_positional_argument_but_2_were_given(self):
|
|
266
|
-
class MyAgg(Aggregate):
|
|
267
|
-
@event
|
|
268
|
-
def value_changed(self):
|
|
269
|
-
pass
|
|
270
|
-
|
|
271
|
-
class Data:
|
|
272
|
-
def value_changed(self):
|
|
273
|
-
pass
|
|
274
|
-
|
|
275
|
-
def assert_raises(cls):
|
|
276
|
-
obj = cls()
|
|
277
|
-
with self.assertRaises(TypeError) as cm:
|
|
278
|
-
obj.value_changed(1)
|
|
279
|
-
|
|
280
|
-
name = get_method_name(cls.value_changed)
|
|
281
|
-
|
|
282
|
-
self.assertEqual(
|
|
283
|
-
f"{name}() takes 1 positional argument but 2 were given",
|
|
284
|
-
cm.exception.args[0],
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
assert_raises(MyAgg)
|
|
288
|
-
assert_raises(Data)
|
|
289
|
-
|
|
290
|
-
def test_raises_when_method_takes_2_positional_argument_but_3_were_given(self):
|
|
291
|
-
class MyAgg(Aggregate):
|
|
292
|
-
@event
|
|
293
|
-
def value_changed(self, value):
|
|
294
|
-
pass
|
|
295
|
-
|
|
296
|
-
class Data:
|
|
297
|
-
def value_changed(self, value):
|
|
298
|
-
pass
|
|
299
|
-
|
|
300
|
-
def assert_raises(cls):
|
|
301
|
-
obj = cls()
|
|
302
|
-
with self.assertRaises(TypeError) as cm:
|
|
303
|
-
obj.value_changed(1, 2)
|
|
304
|
-
name = get_method_name(cls.value_changed)
|
|
305
|
-
self.assertEqual(
|
|
306
|
-
f"{name}() takes 2 positional arguments but 3 were given",
|
|
307
|
-
cm.exception.args[0],
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
assert_raises(MyAgg)
|
|
311
|
-
assert_raises(Data)
|
|
312
|
-
|
|
313
|
-
def test_raises_when_method_missing_1_required_positional_argument(self):
|
|
314
|
-
class MyAgg(Aggregate):
|
|
315
|
-
@event
|
|
316
|
-
def value_changed(self, a):
|
|
317
|
-
pass
|
|
318
|
-
|
|
319
|
-
class Data:
|
|
320
|
-
def value_changed(self, a):
|
|
321
|
-
pass
|
|
322
|
-
|
|
323
|
-
def assert_raises(cls):
|
|
324
|
-
obj = cls()
|
|
325
|
-
with self.assertRaises(TypeError) as cm:
|
|
326
|
-
obj.value_changed()
|
|
327
|
-
name = get_method_name(cls.value_changed)
|
|
328
|
-
self.assertEqual(
|
|
329
|
-
f"{name}() missing 1 required positional argument: 'a'",
|
|
330
|
-
cm.exception.args[0],
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
assert_raises(MyAgg)
|
|
334
|
-
assert_raises(Data)
|
|
335
|
-
|
|
336
|
-
def test_raises_when_method_missing_2_required_positional_arguments(self):
|
|
337
|
-
class MyAgg(Aggregate):
|
|
338
|
-
@event
|
|
339
|
-
def value_changed(self, a, b):
|
|
340
|
-
pass
|
|
341
|
-
|
|
342
|
-
class Data:
|
|
343
|
-
def value_changed(self, a, b):
|
|
344
|
-
pass
|
|
345
|
-
|
|
346
|
-
def assert_raises(cls):
|
|
347
|
-
obj = cls()
|
|
348
|
-
with self.assertRaises(TypeError) as cm:
|
|
349
|
-
obj.value_changed()
|
|
350
|
-
name = get_method_name(obj.value_changed)
|
|
351
|
-
self.assertEqual(
|
|
352
|
-
f"{name}() missing 2 required positional arguments: 'a' and 'b'",
|
|
353
|
-
cm.exception.args[0],
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
assert_raises(MyAgg)
|
|
357
|
-
assert_raises(Data)
|
|
358
|
-
|
|
359
|
-
def test_raises_when_method_missing_3_required_positional_arguments(self):
|
|
360
|
-
class MyAgg(Aggregate):
|
|
361
|
-
@event
|
|
362
|
-
def value_changed(self, a, b, c):
|
|
363
|
-
pass
|
|
364
|
-
|
|
365
|
-
class Data:
|
|
366
|
-
def value_changed(self, a, b, c):
|
|
367
|
-
pass
|
|
368
|
-
|
|
369
|
-
def assert_raises(cls):
|
|
370
|
-
obj = cls()
|
|
371
|
-
with self.assertRaises(TypeError) as cm:
|
|
372
|
-
obj.value_changed()
|
|
373
|
-
|
|
374
|
-
name = get_method_name(cls.value_changed)
|
|
375
|
-
|
|
376
|
-
self.assertEqual(
|
|
377
|
-
f"{name}() missing 3 required positional arguments: 'a', 'b', and 'c'",
|
|
378
|
-
cm.exception.args[0],
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
assert_raises(MyAgg)
|
|
382
|
-
assert_raises(Data)
|
|
383
|
-
|
|
384
|
-
def test_raises_when_method_missing_1_required_keyword_only_argument(self):
|
|
385
|
-
class MyAgg(Aggregate):
|
|
386
|
-
@event
|
|
387
|
-
def value_changed(self, a, *, b):
|
|
388
|
-
pass
|
|
389
|
-
|
|
390
|
-
class Data:
|
|
391
|
-
def value_changed(self, a, *, b):
|
|
392
|
-
pass
|
|
393
|
-
|
|
394
|
-
def assert_raises(cls):
|
|
395
|
-
obj = cls()
|
|
396
|
-
|
|
397
|
-
with self.assertRaises(TypeError) as cm:
|
|
398
|
-
obj.value_changed(1)
|
|
399
|
-
|
|
400
|
-
name = get_method_name(cls.value_changed)
|
|
401
|
-
self.assertEqual(
|
|
402
|
-
f"{name}() missing 1 required keyword-only argument: 'b'",
|
|
403
|
-
cm.exception.args[0],
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
assert_raises(MyAgg)
|
|
407
|
-
assert_raises(Data)
|
|
408
|
-
|
|
409
|
-
def test_raises_when_method_missing_2_required_keyword_only_arguments(self):
|
|
410
|
-
class MyAgg(Aggregate):
|
|
411
|
-
@event
|
|
412
|
-
def value_changed(self, a, *, b, c):
|
|
413
|
-
pass
|
|
414
|
-
|
|
415
|
-
class Data:
|
|
416
|
-
def value_changed(self, a, *, b, c):
|
|
417
|
-
pass
|
|
418
|
-
|
|
419
|
-
def assert_raises(cls):
|
|
420
|
-
obj = cls()
|
|
421
|
-
|
|
422
|
-
with self.assertRaises(TypeError) as cm:
|
|
423
|
-
obj.value_changed(1)
|
|
424
|
-
|
|
425
|
-
name = get_method_name(cls.value_changed)
|
|
426
|
-
self.assertEqual(
|
|
427
|
-
f"{name}() missing 2 required keyword-only arguments: 'b' and 'c'",
|
|
428
|
-
cm.exception.args[0],
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
assert_raises(MyAgg)
|
|
432
|
-
assert_raises(Data)
|
|
433
|
-
|
|
434
|
-
def test_raises_when_method_missing_3_required_keyword_only_arguments(self):
|
|
435
|
-
class MyAgg(Aggregate):
|
|
436
|
-
@event
|
|
437
|
-
def value_changed(self, a, *, b, c, d):
|
|
438
|
-
pass
|
|
439
|
-
|
|
440
|
-
class Data:
|
|
441
|
-
def value_changed(self, a, *, b, c, d):
|
|
442
|
-
pass
|
|
443
|
-
|
|
444
|
-
def assert_raises(cls):
|
|
445
|
-
obj = cls()
|
|
446
|
-
|
|
447
|
-
with self.assertRaises(TypeError) as cm:
|
|
448
|
-
obj.value_changed(1)
|
|
449
|
-
|
|
450
|
-
name = get_method_name(cls.value_changed)
|
|
451
|
-
self.assertEqual(
|
|
452
|
-
f"{name}() missing 3 required keyword-only arguments: "
|
|
453
|
-
"'b', 'c', and 'd'",
|
|
454
|
-
cm.exception.args[0],
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
assert_raises(MyAgg)
|
|
458
|
-
assert_raises(Data)
|
|
459
|
-
|
|
460
|
-
def test_raises_when_missing_positional_and_required_keyword_only_arguments(self):
|
|
461
|
-
class MyAgg(Aggregate):
|
|
462
|
-
@event
|
|
463
|
-
def value_changed(self, a, *, b, c, d):
|
|
464
|
-
pass
|
|
465
|
-
|
|
466
|
-
class Data:
|
|
467
|
-
def value_changed(self, a, *, b, c, d):
|
|
468
|
-
pass
|
|
469
|
-
|
|
470
|
-
def assert_raises(cls):
|
|
471
|
-
obj = cls()
|
|
472
|
-
|
|
473
|
-
with self.assertRaises(TypeError) as cm:
|
|
474
|
-
obj.value_changed()
|
|
475
|
-
|
|
476
|
-
name = get_method_name(cls.value_changed)
|
|
477
|
-
self.assertEqual(
|
|
478
|
-
f"{name}() missing 1 required positional argument: 'a'",
|
|
479
|
-
cm.exception.args[0],
|
|
480
|
-
)
|
|
481
|
-
|
|
482
|
-
assert_raises(MyAgg)
|
|
483
|
-
assert_raises(Data)
|
|
484
|
-
|
|
485
|
-
def test_raises_when_method_gets_unexpected_keyword_argument(self):
|
|
486
|
-
class Data:
|
|
487
|
-
def value_changed(self, a):
|
|
488
|
-
pass
|
|
489
|
-
|
|
490
|
-
class MyAgg(Aggregate):
|
|
491
|
-
@event
|
|
492
|
-
def value_changed(self, a):
|
|
493
|
-
pass
|
|
494
|
-
|
|
495
|
-
def assert_raises(cls):
|
|
496
|
-
obj = cls()
|
|
497
|
-
|
|
498
|
-
with self.assertRaises(TypeError) as cm:
|
|
499
|
-
obj.value_changed(b=1)
|
|
500
|
-
|
|
501
|
-
name = get_method_name(cls.value_changed)
|
|
502
|
-
self.assertEqual(
|
|
503
|
-
f"{name}() got an unexpected keyword argument 'b'",
|
|
504
|
-
cm.exception.args[0],
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
assert_raises(MyAgg)
|
|
508
|
-
assert_raises(Data)
|
|
509
|
-
|
|
510
|
-
def test_raises_when_method_is_staticmethod(self):
|
|
511
|
-
with self.assertRaises(TypeError) as cm:
|
|
512
|
-
|
|
513
|
-
class _(Aggregate): # noqa: N801
|
|
514
|
-
@event
|
|
515
|
-
@staticmethod
|
|
516
|
-
def value_changed():
|
|
517
|
-
pass
|
|
518
|
-
|
|
519
|
-
self.assertIn(
|
|
520
|
-
"is not a str, function, property, or subclass of CanMutateAggregate",
|
|
521
|
-
cm.exception.args[0],
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
with self.assertRaises(TypeError) as cm:
|
|
525
|
-
|
|
526
|
-
class MyAgg(Aggregate):
|
|
527
|
-
@event("ValueChanged")
|
|
528
|
-
@staticmethod
|
|
529
|
-
def value_changed():
|
|
530
|
-
pass
|
|
531
|
-
|
|
532
|
-
self.assertTrue(
|
|
533
|
-
cm.exception.args[0].endswith(
|
|
534
|
-
" is not a function or property",
|
|
535
|
-
),
|
|
536
|
-
cm.exception.args[0],
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
def test_raises_when_method_is_classmethod(self):
|
|
540
|
-
with self.assertRaises(TypeError) as cm:
|
|
541
|
-
|
|
542
|
-
class _(Aggregate): # noqa: N801
|
|
543
|
-
@event
|
|
544
|
-
@classmethod
|
|
545
|
-
def value_changed(cls):
|
|
546
|
-
pass
|
|
547
|
-
|
|
548
|
-
self.assertIn(
|
|
549
|
-
"is not a str, function, property, or subclass of CanMutateAggregate",
|
|
550
|
-
cm.exception.args[0],
|
|
551
|
-
)
|
|
552
|
-
|
|
553
|
-
with self.assertRaises(TypeError) as cm:
|
|
554
|
-
|
|
555
|
-
class MyAgg(Aggregate):
|
|
556
|
-
@event("ValueChanged")
|
|
557
|
-
@classmethod
|
|
558
|
-
def value_changed(cls):
|
|
559
|
-
pass
|
|
560
|
-
|
|
561
|
-
self.assertTrue(
|
|
562
|
-
cm.exception.args[0].endswith(
|
|
563
|
-
" is not a function or property",
|
|
564
|
-
),
|
|
565
|
-
cm.exception.args[0],
|
|
566
|
-
)
|
|
567
|
-
|
|
568
|
-
def test_method_called_with_positional_defined_with_keyword_params(self):
|
|
569
|
-
class MyAgg(Aggregate):
|
|
570
|
-
@event
|
|
571
|
-
def values_changed(self, a=None, b=None):
|
|
572
|
-
self.a = a
|
|
573
|
-
self.b = b
|
|
574
|
-
|
|
575
|
-
a = MyAgg()
|
|
576
|
-
a.values_changed(1, 2)
|
|
577
|
-
|
|
578
|
-
def test_method_called_with_keyword_defined_with_positional_params(self):
|
|
579
|
-
class MyAgg(Aggregate):
|
|
580
|
-
@event
|
|
581
|
-
def values_changed(self, a, b):
|
|
582
|
-
self.a = a
|
|
583
|
-
self.b = b
|
|
584
|
-
|
|
585
|
-
a = MyAgg()
|
|
586
|
-
a.values_changed(a=1, b=2)
|
|
587
|
-
|
|
588
|
-
# @skipIf(sys.version_info[0:2] < (3, 8), "Positional only params not supported")
|
|
589
|
-
# def test_method_called_with_keyword_defined_with_positional_only(self):
|
|
590
|
-
# @aggregate
|
|
591
|
-
# class MyAgg:
|
|
592
|
-
# @event
|
|
593
|
-
# def values_changed(self, a, b, /):
|
|
594
|
-
# self.a = a
|
|
595
|
-
# self.b = b
|
|
596
|
-
#
|
|
597
|
-
# a = MyAgg()
|
|
598
|
-
# a.values_changed(1, 2)
|
|
599
|
-
|
|
600
|
-
# def test_raises_when_method_has_positional_only_params(self):
|
|
601
|
-
# @aggregate
|
|
602
|
-
# class MyAgg:
|
|
603
|
-
# @event
|
|
604
|
-
# def values_changed(self, a, b, /):
|
|
605
|
-
# self.a = a
|
|
606
|
-
# self.b = b
|
|
607
|
-
#
|
|
608
|
-
# with self.assertRaises(TypeError) as cm:
|
|
609
|
-
#
|
|
610
|
-
# a = MyAgg()
|
|
611
|
-
# a.values_changed(1, 2)
|
|
612
|
-
#
|
|
613
|
-
# self.assertTrue(
|
|
614
|
-
# cm.exception.args[0].startswith(
|
|
615
|
-
# # "values_changed() got some positional-only arguments"
|
|
616
|
-
# "Can't construct event"
|
|
617
|
-
# ),
|
|
618
|
-
# cm.exception.args[0],
|
|
619
|
-
# )
|
|
620
|
-
|
|
621
|
-
def test_raises_when_decorated_method_called_directly_without_instance_arg(self):
|
|
622
|
-
class MyAgg(Aggregate):
|
|
623
|
-
@event
|
|
624
|
-
def method(self):
|
|
625
|
-
pass
|
|
626
|
-
|
|
627
|
-
with self.assertRaises(TypeError) as cm:
|
|
628
|
-
MyAgg.method()
|
|
629
|
-
self.assertEqual(
|
|
630
|
-
cm.exception.args[0],
|
|
631
|
-
"Expected aggregate as first argument",
|
|
632
|
-
)
|
|
633
|
-
|
|
634
|
-
def test_decorated_method_called_directly_on_class(self):
|
|
635
|
-
class MyAgg(Aggregate):
|
|
636
|
-
@event
|
|
637
|
-
def method(self):
|
|
638
|
-
pass
|
|
639
|
-
|
|
640
|
-
a = MyAgg()
|
|
641
|
-
self.assertEqual(a.version, 1)
|
|
642
|
-
MyAgg.method(a)
|
|
643
|
-
self.assertEqual(a.version, 2)
|
|
644
|
-
|
|
645
|
-
def test_event_name_set_in_decorator(self):
|
|
646
|
-
class MyAgg(Aggregate):
|
|
647
|
-
@event("ValueChanged")
|
|
648
|
-
def set_value(self, value):
|
|
649
|
-
self.value = value
|
|
650
|
-
|
|
651
|
-
a = MyAgg()
|
|
652
|
-
a.set_value(value=1)
|
|
653
|
-
self.assertEqual(a.value, 1)
|
|
654
|
-
self.assertIsInstance(a, Aggregate)
|
|
655
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
656
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
657
|
-
|
|
658
|
-
def test_event_called_to_redefine_method_with_explicit_name(self):
|
|
659
|
-
class MyAgg(Aggregate):
|
|
660
|
-
def set_value(self, value):
|
|
661
|
-
self.value = value
|
|
662
|
-
|
|
663
|
-
set_value = event("ValueChanged")(set_value)
|
|
664
|
-
|
|
665
|
-
a = MyAgg()
|
|
666
|
-
a.set_value(value=1)
|
|
667
|
-
self.assertEqual(a.value, 1)
|
|
668
|
-
self.assertIsInstance(a, Aggregate)
|
|
669
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
670
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
671
|
-
|
|
672
|
-
def test_event_called_to_redefine_method_with_implied_name(self):
|
|
673
|
-
class MyAgg(Aggregate):
|
|
674
|
-
def value_changed(self, value):
|
|
675
|
-
self.value = value
|
|
676
|
-
|
|
677
|
-
set_value = event(value_changed)
|
|
678
|
-
|
|
679
|
-
a = MyAgg()
|
|
680
|
-
a.set_value(value=1)
|
|
681
|
-
self.assertEqual(a.value, 1)
|
|
682
|
-
self.assertIsInstance(a, Aggregate)
|
|
683
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
684
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
685
|
-
|
|
686
|
-
def test_event_name_set_in_decorator_cannot_be_empty_string(self):
|
|
687
|
-
with self.assertRaises(ValueError) as cm:
|
|
688
|
-
|
|
689
|
-
class MyAgg(Aggregate):
|
|
690
|
-
@event("")
|
|
691
|
-
def set_value(self, value):
|
|
692
|
-
self.value = value
|
|
693
|
-
|
|
694
|
-
self.assertEqual(
|
|
695
|
-
cm.exception.args[0], "Can't use empty string as name of event class"
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
def test_event_with_name_decorates_property(self):
|
|
699
|
-
class MyAgg(Aggregate):
|
|
700
|
-
def __init__(self, value):
|
|
701
|
-
self._value = value
|
|
702
|
-
|
|
703
|
-
@property
|
|
704
|
-
def value(self):
|
|
705
|
-
return self._value
|
|
706
|
-
|
|
707
|
-
@event("ValueChanged")
|
|
708
|
-
@value.setter
|
|
709
|
-
def value(self, x):
|
|
710
|
-
self._value = x
|
|
711
|
-
|
|
712
|
-
a = MyAgg(0)
|
|
713
|
-
self.assertEqual(a.value, 0)
|
|
714
|
-
a.value = 1
|
|
715
|
-
self.assertEqual(a.value, 1)
|
|
716
|
-
self.assertIsInstance(a, Aggregate)
|
|
717
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
718
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
719
|
-
|
|
720
|
-
def test_property_decorates_event_with_name(self):
|
|
721
|
-
class MyAgg(Aggregate):
|
|
722
|
-
@property
|
|
723
|
-
def value(self):
|
|
724
|
-
return self._value
|
|
725
|
-
|
|
726
|
-
@value.setter
|
|
727
|
-
@event("ValueChanged")
|
|
728
|
-
def value(self, x):
|
|
729
|
-
self._value = x
|
|
730
|
-
|
|
731
|
-
a = MyAgg()
|
|
732
|
-
a.value = 1
|
|
733
|
-
self.assertEqual(a.value, 1)
|
|
734
|
-
self.assertIsInstance(a, Aggregate)
|
|
735
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
736
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
737
|
-
|
|
738
|
-
def test_property_called_with_decorated_set_method_with_name_given(self):
|
|
739
|
-
class MyAgg(Aggregate):
|
|
740
|
-
def get_value(self):
|
|
741
|
-
return self._value
|
|
742
|
-
|
|
743
|
-
@event("ValueChanged")
|
|
744
|
-
def set_value(self, x):
|
|
745
|
-
self._value = x
|
|
746
|
-
|
|
747
|
-
value = property(get_value, set_value)
|
|
748
|
-
|
|
749
|
-
a = MyAgg()
|
|
750
|
-
a.value = 1
|
|
751
|
-
self.assertEqual(a.value, 1)
|
|
752
|
-
self.assertIsInstance(a, Aggregate)
|
|
753
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
754
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
755
|
-
|
|
756
|
-
def test_property_called_with_decorated_set_method_with_name_inferred(self):
|
|
757
|
-
class MyAgg(Aggregate):
|
|
758
|
-
def get_value(self):
|
|
759
|
-
return self._value
|
|
760
|
-
|
|
761
|
-
@event
|
|
762
|
-
def value_changed(self, x):
|
|
763
|
-
self._value = x
|
|
764
|
-
|
|
765
|
-
value = property(get_value, value_changed)
|
|
766
|
-
|
|
767
|
-
a = MyAgg()
|
|
768
|
-
a.value = 1
|
|
769
|
-
self.assertEqual(a.value, 1)
|
|
770
|
-
self.assertIsInstance(a, Aggregate)
|
|
771
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
772
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
773
|
-
|
|
774
|
-
def test_property_called_with_wrapped_set_method_with_name_given(self):
|
|
775
|
-
class MyAgg(Aggregate):
|
|
776
|
-
def get_value(self):
|
|
777
|
-
return self._value
|
|
778
|
-
|
|
779
|
-
def set_value(self, x):
|
|
780
|
-
self._value = x
|
|
781
|
-
|
|
782
|
-
value = property(get_value, event("ValueChanged")(set_value))
|
|
783
|
-
|
|
784
|
-
a = MyAgg()
|
|
785
|
-
a.value = 1
|
|
786
|
-
self.assertEqual(a.value, 1)
|
|
787
|
-
self.assertIsInstance(a, Aggregate)
|
|
788
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
789
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
790
|
-
|
|
791
|
-
def test_property_called_with_wrapped_set_method_with_name_inferred(self):
|
|
792
|
-
class MyAgg(Aggregate):
|
|
793
|
-
def get_value(self):
|
|
794
|
-
return self._value
|
|
795
|
-
|
|
796
|
-
def value_changed(self, x):
|
|
797
|
-
self._value = x
|
|
798
|
-
|
|
799
|
-
value = property(get_value, event(value_changed))
|
|
800
|
-
|
|
801
|
-
a = MyAgg()
|
|
802
|
-
a.value = 1
|
|
803
|
-
self.assertEqual(a.value, 1)
|
|
804
|
-
self.assertIsInstance(a, Aggregate)
|
|
805
|
-
self.assertEqual(len(a.pending_events), 2)
|
|
806
|
-
self.assertIsInstance(a.pending_events[1], MyAgg.ValueChanged)
|
|
807
|
-
|
|
808
|
-
def test_raises_when_event_decorates_property_getter(self):
|
|
809
|
-
with self.assertRaises(TypeError) as cm:
|
|
810
|
-
|
|
811
|
-
class MyAgg(Aggregate):
|
|
812
|
-
@event("ValueChanged")
|
|
813
|
-
@property
|
|
814
|
-
def value(self):
|
|
815
|
-
return None
|
|
816
|
-
|
|
817
|
-
self.assertEqual(
|
|
818
|
-
cm.exception.args[0], "@event can't decorate value() property getter"
|
|
819
|
-
)
|
|
820
|
-
|
|
821
|
-
with self.assertRaises(TypeError) as cm:
|
|
822
|
-
|
|
823
|
-
@aggregate
|
|
824
|
-
class _: # noqa: N801
|
|
825
|
-
@event("ValueChanged")
|
|
826
|
-
@property
|
|
827
|
-
def value(self):
|
|
828
|
-
return None
|
|
829
|
-
|
|
830
|
-
self.assertEqual(
|
|
831
|
-
cm.exception.args[0], "@event can't decorate value() property getter"
|
|
832
|
-
)
|
|
833
|
-
|
|
834
|
-
def test_raises_when_event_without_name_decorates_property(self):
|
|
835
|
-
with self.assertRaises(TypeError) as cm:
|
|
836
|
-
|
|
837
|
-
class MyAgg(Aggregate):
|
|
838
|
-
def __init__(self, _):
|
|
839
|
-
pass
|
|
840
|
-
|
|
841
|
-
@property
|
|
842
|
-
def value(self):
|
|
843
|
-
return None
|
|
844
|
-
|
|
845
|
-
@event
|
|
846
|
-
@value.setter
|
|
847
|
-
def value(self, x):
|
|
848
|
-
pass
|
|
849
|
-
|
|
850
|
-
self.assertEqual(
|
|
851
|
-
cm.exception.args[0],
|
|
852
|
-
"@event on value() setter requires event name or class",
|
|
853
|
-
)
|
|
854
|
-
|
|
855
|
-
def test_raises_when_property_decorates_event_without_name(self):
|
|
856
|
-
with self.assertRaises(TypeError) as cm:
|
|
857
|
-
|
|
858
|
-
class MyAgg(Aggregate):
|
|
859
|
-
def __init__(self, _):
|
|
860
|
-
pass
|
|
861
|
-
|
|
862
|
-
@property
|
|
863
|
-
def value(self):
|
|
864
|
-
return None
|
|
865
|
-
|
|
866
|
-
@value.setter
|
|
867
|
-
@event
|
|
868
|
-
def value(self, _):
|
|
869
|
-
pass
|
|
870
|
-
|
|
871
|
-
self.assertEqual(
|
|
872
|
-
cm.exception.args[0],
|
|
873
|
-
"@event under value() property setter requires event class name",
|
|
874
|
-
)
|
|
875
|
-
|
|
876
|
-
def test_raises_when_event_decorator_used_with_wrong_args(self):
|
|
877
|
-
with self.assertRaises(TypeError) as cm:
|
|
878
|
-
event(1)
|
|
879
|
-
self.assertEqual(
|
|
880
|
-
"1 is not a str, function, property, or subclass of CanMutateAggregate",
|
|
881
|
-
cm.exception.args[0],
|
|
882
|
-
)
|
|
883
|
-
|
|
884
|
-
with self.assertRaises(TypeError) as cm:
|
|
885
|
-
event("EventName")(1)
|
|
886
|
-
self.assertEqual(
|
|
887
|
-
"1 is not a function or property",
|
|
888
|
-
cm.exception.args[0],
|
|
889
|
-
)
|
|
890
|
-
|
|
891
|
-
def test_raises_when_decorated_method_has_variable_args(self):
|
|
892
|
-
with self.assertRaises(TypeError) as cm:
|
|
893
|
-
|
|
894
|
-
class _(Aggregate): # noqa: N801
|
|
895
|
-
@event # no event name
|
|
896
|
-
def method(self, *args):
|
|
897
|
-
pass
|
|
898
|
-
|
|
899
|
-
self.assertEqual(
|
|
900
|
-
cm.exception.args[0], "*args not supported by decorator on method()"
|
|
901
|
-
)
|
|
902
|
-
|
|
903
|
-
with self.assertRaises(TypeError) as cm:
|
|
904
|
-
|
|
905
|
-
class _(Aggregate): # noqa: N801
|
|
906
|
-
@event("EventName") # has event name
|
|
907
|
-
def method(self, *args):
|
|
908
|
-
pass
|
|
909
|
-
|
|
910
|
-
self.assertEqual(
|
|
911
|
-
cm.exception.args[0], "*args not supported by decorator on method()"
|
|
912
|
-
)
|
|
913
|
-
|
|
914
|
-
def test_raises_when_decorated_method_has_variable_kwargs(self):
|
|
915
|
-
with self.assertRaises(TypeError) as cm:
|
|
916
|
-
|
|
917
|
-
class _(Aggregate): # noqa: N801
|
|
918
|
-
@event # no event name
|
|
919
|
-
def method(self, **kwargs):
|
|
920
|
-
pass
|
|
921
|
-
|
|
922
|
-
self.assertEqual(
|
|
923
|
-
cm.exception.args[0], "**kwargs not supported by decorator on method()"
|
|
924
|
-
)
|
|
925
|
-
|
|
926
|
-
with self.assertRaises(TypeError) as cm:
|
|
927
|
-
|
|
928
|
-
class _(Aggregate): # noqa: N801
|
|
929
|
-
@event("EventName") # has event name
|
|
930
|
-
def method(self, **kwargs):
|
|
931
|
-
pass
|
|
932
|
-
|
|
933
|
-
self.assertEqual(
|
|
934
|
-
cm.exception.args[0], "**kwargs not supported by decorator on method()"
|
|
935
|
-
)
|
|
936
|
-
|
|
937
|
-
# With property.
|
|
938
|
-
with self.assertRaises(TypeError) as cm:
|
|
939
|
-
|
|
940
|
-
class _(Aggregate): # noqa: N801
|
|
941
|
-
@property
|
|
942
|
-
def name(self):
|
|
943
|
-
return None
|
|
944
|
-
|
|
945
|
-
@event("EventName") # before setter
|
|
946
|
-
@name.setter
|
|
947
|
-
def name(self, **kwargs):
|
|
948
|
-
pass
|
|
949
|
-
|
|
950
|
-
self.assertEqual(
|
|
951
|
-
cm.exception.args[0], "**kwargs not supported by decorator on name()"
|
|
952
|
-
)
|
|
953
|
-
|
|
954
|
-
with self.assertRaises(TypeError) as cm:
|
|
955
|
-
|
|
956
|
-
class _(Aggregate): # noqa: N801
|
|
957
|
-
@property
|
|
958
|
-
def name(self):
|
|
959
|
-
return None
|
|
960
|
-
|
|
961
|
-
@name.setter
|
|
962
|
-
@event("EventName") # after setter (same as without property)
|
|
963
|
-
def name(self, **kwargs):
|
|
964
|
-
pass
|
|
965
|
-
|
|
966
|
-
self.assertEqual(
|
|
967
|
-
cm.exception.args[0], "**kwargs not supported by decorator on name()"
|
|
968
|
-
)
|
|
969
|
-
|
|
970
|
-
# TODO: Somehow deal with custom decorators?
|
|
971
|
-
# def test_custom_decorators(self):
|
|
972
|
-
#
|
|
973
|
-
# def mydecorator(f):
|
|
974
|
-
# def g(*args, **kwargs):
|
|
975
|
-
# f(*args, **kwargs)
|
|
976
|
-
# return g
|
|
977
|
-
#
|
|
978
|
-
# @aggregate
|
|
979
|
-
# class MyAgg:
|
|
980
|
-
# @event
|
|
981
|
-
# @mydecorator
|
|
982
|
-
# def method(self):
|
|
983
|
-
# raise Exception("Shou")
|
|
984
|
-
#
|
|
985
|
-
# a = MyAgg()
|
|
986
|
-
# a.method()
|
|
987
|
-
#
|
|
988
|
-
|
|
989
|
-
def test_event_decorator_uses_explicit_event_classes(self) -> None:
|
|
990
|
-
# Here we just use the @event decorator to trigger events
|
|
991
|
-
# that are applied using the decorated method.
|
|
992
|
-
@aggregate
|
|
993
|
-
class Order:
|
|
994
|
-
class Confirmed(AggregateEvent):
|
|
995
|
-
at: datetime
|
|
996
|
-
|
|
997
|
-
@triggers(Confirmed)
|
|
998
|
-
def confirm(self, at):
|
|
999
|
-
self.confirmed_at = at
|
|
1000
|
-
|
|
1001
|
-
order = Order()
|
|
1002
|
-
|
|
1003
|
-
order.confirm(AggregateEvent.create_timestamp())
|
|
1004
|
-
self.assertIsInstance(order.confirmed_at, datetime)
|
|
1005
|
-
|
|
1006
|
-
app: Application = Application()
|
|
1007
|
-
app.save(order)
|
|
1008
|
-
|
|
1009
|
-
copy = app.repository.get(order.id)
|
|
1010
|
-
|
|
1011
|
-
self.assertEqual(copy.confirmed_at, order.confirmed_at)
|
|
1012
|
-
|
|
1013
|
-
self.assertIsInstance(order, Aggregate)
|
|
1014
|
-
self.assertIsInstance(order, Order)
|
|
1015
|
-
self.assertIsInstance(copy, Aggregate)
|
|
1016
|
-
self.assertIsInstance(copy, Order)
|
|
1017
|
-
|
|
1018
|
-
# def test_raises_when_event_class_has_apply_method(self) -> None:
|
|
1019
|
-
# # Check raises when defining an apply method on an
|
|
1020
|
-
# # event used in a decorator when aggregate inherits
|
|
1021
|
-
# # from Aggregate class.
|
|
1022
|
-
# with self.assertRaises(TypeError) as cm:
|
|
1023
|
-
#
|
|
1024
|
-
# class _(Aggregate):
|
|
1025
|
-
# class Confirmed(AggregateEvent):
|
|
1026
|
-
# def apply(self, aggregate):
|
|
1027
|
-
# pass
|
|
1028
|
-
#
|
|
1029
|
-
# @triggers(Confirmed)
|
|
1030
|
-
# def confirm(self):
|
|
1031
|
-
# pass
|
|
1032
|
-
#
|
|
1033
|
-
# self.assertEqual(
|
|
1034
|
-
# cm.exception.args[0], "event class has unexpected apply() method"
|
|
1035
|
-
# )
|
|
1036
|
-
|
|
1037
|
-
def test_raises_when_event_class_already_defined(self) -> None:
|
|
1038
|
-
# Here we just use the @event decorator to trigger events
|
|
1039
|
-
# that are applied using the decorated method.
|
|
1040
|
-
with self.assertRaises(TypeError) as cm:
|
|
1041
|
-
|
|
1042
|
-
@aggregate
|
|
1043
|
-
class Order(Aggregate):
|
|
1044
|
-
class Confirmed(AggregateEvent):
|
|
1045
|
-
at: datetime
|
|
1046
|
-
|
|
1047
|
-
@triggers("Confirmed")
|
|
1048
|
-
def confirm(self, at):
|
|
1049
|
-
self.confirmed_at = at
|
|
1050
|
-
|
|
1051
|
-
self.assertEqual(
|
|
1052
|
-
cm.exception.args[0], "Confirmed event already defined on Order"
|
|
1053
|
-
)
|
|
1054
|
-
|
|
1055
|
-
def test_raises_when_event_class_name_used_twice(self) -> None:
|
|
1056
|
-
# Here we make sure the same event class name can't be
|
|
1057
|
-
# declared on two decorators.
|
|
1058
|
-
with self.assertRaises(TypeError) as cm:
|
|
1059
|
-
|
|
1060
|
-
@aggregate
|
|
1061
|
-
class Order(Aggregate):
|
|
1062
|
-
@triggers("Confirmed")
|
|
1063
|
-
def confirm1(self, at):
|
|
1064
|
-
self.confirmed_at = at
|
|
1065
|
-
|
|
1066
|
-
@triggers("Confirmed")
|
|
1067
|
-
def confirm2(self, at):
|
|
1068
|
-
self.confirmed_at = at
|
|
1069
|
-
|
|
1070
|
-
self.assertEqual(
|
|
1071
|
-
cm.exception.args[0], "Confirmed event already defined on Order"
|
|
1072
|
-
)
|
|
1073
|
-
|
|
1074
|
-
def test_raises_when_event_class_used_twice(self) -> None:
|
|
1075
|
-
# Here we make sure the same event class can't be
|
|
1076
|
-
# mentioned on two decorators.
|
|
1077
|
-
with self.assertRaises(TypeError) as cm:
|
|
1078
|
-
|
|
1079
|
-
@aggregate
|
|
1080
|
-
class Order(Aggregate):
|
|
1081
|
-
class Confirmed(AggregateEvent):
|
|
1082
|
-
at: datetime
|
|
1083
|
-
|
|
1084
|
-
@triggers(Confirmed)
|
|
1085
|
-
def confirm1(self, at):
|
|
1086
|
-
self.confirmed_at = at
|
|
1087
|
-
|
|
1088
|
-
@triggers(Confirmed)
|
|
1089
|
-
def confirm2(self, at):
|
|
1090
|
-
self.confirmed_at = at
|
|
1091
|
-
|
|
1092
|
-
self.assertEqual(
|
|
1093
|
-
cm.exception.args[0],
|
|
1094
|
-
"Confirmed event class used in more than one decorator",
|
|
1095
|
-
)
|
|
1096
|
-
|
|
1097
|
-
def test_dirty_style_isnt_so_dirty_after_all(self):
|
|
1098
|
-
class Order(Aggregate):
|
|
1099
|
-
def __init__(self, name):
|
|
1100
|
-
self.name = name
|
|
1101
|
-
self.confirmed_at = None
|
|
1102
|
-
self.pickedup_at = None
|
|
1103
|
-
|
|
1104
|
-
@event("Confirmed")
|
|
1105
|
-
def confirm(self, at):
|
|
1106
|
-
self.confirmed_at = at
|
|
1107
|
-
|
|
1108
|
-
@event("PickedUp")
|
|
1109
|
-
def pickup(self, at):
|
|
1110
|
-
if self.confirmed_at is None:
|
|
1111
|
-
msg = "Order is not confirmed"
|
|
1112
|
-
raise RuntimeError(msg)
|
|
1113
|
-
self.pickedup_at = at
|
|
1114
|
-
|
|
1115
|
-
order = Order("name")
|
|
1116
|
-
self.assertEqual(len(order.pending_events), 1)
|
|
1117
|
-
with contextlib.suppress(RuntimeError):
|
|
1118
|
-
order.pickup(AggregateEvent.create_timestamp())
|
|
1119
|
-
self.assertEqual(len(order.pending_events), 1)
|
|
1120
|
-
|
|
1121
|
-
def test_aggregate_has_a_created_event_name_defined_with_event_decorator(self):
|
|
1122
|
-
class MyAggregate(Aggregate):
|
|
1123
|
-
@event("Started")
|
|
1124
|
-
def __init__(self):
|
|
1125
|
-
pass
|
|
1126
|
-
|
|
1127
|
-
a = MyAggregate()
|
|
1128
|
-
self.assertEqual(type(a.pending_events[0]).__name__, "Started")
|
|
1129
|
-
|
|
1130
|
-
self.assertTrue(hasattr(MyAggregate, "_created_event_class"))
|
|
1131
|
-
created_event_cls = MyAggregate._created_event_class
|
|
1132
|
-
self.assertEqual(created_event_cls.__name__, "Started")
|
|
1133
|
-
self.assertTrue(created_event_cls.__qualname__.endswith("MyAggregate.Started"))
|
|
1134
|
-
self.assertTrue(issubclass(created_event_cls, AggregateCreated))
|
|
1135
|
-
self.assertEqual(created_event_cls, MyAggregate.Started)
|
|
1136
|
-
|
|
1137
|
-
def test_aggregate_has_a_created_event_class_mentioned_event_decorator(self):
|
|
1138
|
-
class MyAggregate(Aggregate):
|
|
1139
|
-
class Started(AggregateCreated):
|
|
1140
|
-
pass
|
|
1141
|
-
|
|
1142
|
-
class Opened(AggregateCreated):
|
|
1143
|
-
pass
|
|
1144
|
-
|
|
1145
|
-
@event(Started)
|
|
1146
|
-
def __init__(self):
|
|
1147
|
-
pass
|
|
1148
|
-
|
|
1149
|
-
a = MyAggregate()
|
|
1150
|
-
self.assertEqual(type(a.pending_events[0]).__name__, "Started")
|
|
1151
|
-
|
|
1152
|
-
self.assertTrue(hasattr(MyAggregate, "_created_event_class"))
|
|
1153
|
-
created_event_cls = MyAggregate._created_event_class
|
|
1154
|
-
self.assertEqual(created_event_cls.__name__, "Started")
|
|
1155
|
-
self.assertTrue(created_event_cls.__qualname__.endswith("MyAggregate.Started"))
|
|
1156
|
-
self.assertTrue(issubclass(created_event_cls, AggregateCreated))
|
|
1157
|
-
self.assertEqual(created_event_cls, MyAggregate.Started)
|
|
1158
|
-
|
|
1159
|
-
def test_aggregate_has_incompatible_created_event_class_in_event_decorator(self):
|
|
1160
|
-
class MyAggregate1(Aggregate):
|
|
1161
|
-
class Started(AggregateCreated):
|
|
1162
|
-
a: int
|
|
1163
|
-
|
|
1164
|
-
@event(Started)
|
|
1165
|
-
def __init__(self):
|
|
1166
|
-
pass
|
|
1167
|
-
|
|
1168
|
-
with self.assertRaises(TypeError) as cm:
|
|
1169
|
-
MyAggregate1()
|
|
1170
|
-
self.assertTrue(
|
|
1171
|
-
cm.exception.args[0].startswith("Unable to construct 'Started' event")
|
|
1172
|
-
)
|
|
1173
|
-
|
|
1174
|
-
with self.assertRaises(TypeError) as cm:
|
|
1175
|
-
MyAggregate1(a=1)
|
|
1176
|
-
|
|
1177
|
-
method_name = get_method_name(MyAggregate1.__init__)
|
|
1178
|
-
self.assertEqual(
|
|
1179
|
-
f"{method_name}() got an unexpected keyword argument 'a'",
|
|
1180
|
-
cm.exception.args[0],
|
|
1181
|
-
)
|
|
1182
|
-
|
|
1183
|
-
class MyAggregate2(Aggregate):
|
|
1184
|
-
class Started(AggregateCreated):
|
|
1185
|
-
pass
|
|
1186
|
-
|
|
1187
|
-
@event(Started)
|
|
1188
|
-
def __init__(self, a: int):
|
|
1189
|
-
self.a = a
|
|
1190
|
-
|
|
1191
|
-
with self.assertRaises(TypeError) as cm:
|
|
1192
|
-
MyAggregate2()
|
|
1193
|
-
|
|
1194
|
-
method_name = get_method_name(MyAggregate2.__init__)
|
|
1195
|
-
self.assertEqual(
|
|
1196
|
-
f"{method_name}() missing 1 required positional argument: 'a'",
|
|
1197
|
-
cm.exception.args[0],
|
|
1198
|
-
)
|
|
1199
|
-
|
|
1200
|
-
with self.assertRaises(TypeError) as cm:
|
|
1201
|
-
MyAggregate2(a=1)
|
|
1202
|
-
self.assertTrue(
|
|
1203
|
-
cm.exception.args[0].startswith("Unable to construct 'Started' event:"),
|
|
1204
|
-
cm.exception.args[0],
|
|
1205
|
-
)
|
|
1206
|
-
|
|
1207
|
-
def test_raises_when_using_created_event_class_and_init_event_decorator(self):
|
|
1208
|
-
# Different name.
|
|
1209
|
-
with self.assertRaises(TypeError) as cm:
|
|
1210
|
-
|
|
1211
|
-
class _(Aggregate): # noqa: N801
|
|
1212
|
-
class Started(AggregateCreated):
|
|
1213
|
-
a: int
|
|
1214
|
-
|
|
1215
|
-
class Opened(AggregateCreated):
|
|
1216
|
-
a: int
|
|
1217
|
-
|
|
1218
|
-
_created_event_class = Opened
|
|
1219
|
-
|
|
1220
|
-
@event("Started")
|
|
1221
|
-
def __init__(self):
|
|
1222
|
-
pass
|
|
1223
|
-
|
|
1224
|
-
self.assertEqual(
|
|
1225
|
-
cm.exception.args[0],
|
|
1226
|
-
"Can't use both '_created_event_class' and decorator on __init__",
|
|
1227
|
-
)
|
|
1228
|
-
|
|
1229
|
-
# Same name.
|
|
1230
|
-
with self.assertRaises(TypeError) as cm:
|
|
1231
|
-
|
|
1232
|
-
class _(Aggregate): # noqa: N801
|
|
1233
|
-
class Started(AggregateCreated):
|
|
1234
|
-
a: int
|
|
1235
|
-
|
|
1236
|
-
class Opened(AggregateCreated):
|
|
1237
|
-
a: int
|
|
1238
|
-
|
|
1239
|
-
_created_event_class = Opened
|
|
1240
|
-
|
|
1241
|
-
@event("Opened")
|
|
1242
|
-
def __init__(self):
|
|
1243
|
-
pass
|
|
1244
|
-
|
|
1245
|
-
self.assertEqual(
|
|
1246
|
-
cm.exception.args[0],
|
|
1247
|
-
"Can't use both '_created_event_class' and decorator on __init__",
|
|
1248
|
-
)
|
|
1249
|
-
|
|
1250
|
-
# Different class.
|
|
1251
|
-
with self.assertRaises(TypeError) as cm:
|
|
1252
|
-
|
|
1253
|
-
class _(Aggregate): # noqa: N801
|
|
1254
|
-
class Started(AggregateCreated):
|
|
1255
|
-
a: int
|
|
1256
|
-
|
|
1257
|
-
class Opened(AggregateCreated):
|
|
1258
|
-
a: int
|
|
1259
|
-
|
|
1260
|
-
_created_event_class = Opened
|
|
1261
|
-
|
|
1262
|
-
@event(Started)
|
|
1263
|
-
def __init__(self):
|
|
1264
|
-
pass
|
|
1265
|
-
|
|
1266
|
-
self.assertEqual(
|
|
1267
|
-
cm.exception.args[0],
|
|
1268
|
-
"Can't use both '_created_event_class' and decorator on __init__",
|
|
1269
|
-
)
|
|
1270
|
-
|
|
1271
|
-
# Same class.
|
|
1272
|
-
with self.assertRaises(TypeError) as cm:
|
|
1273
|
-
|
|
1274
|
-
class _(Aggregate): # noqa: N801
|
|
1275
|
-
class Started(AggregateCreated):
|
|
1276
|
-
a: int
|
|
1277
|
-
|
|
1278
|
-
class Opened(AggregateCreated):
|
|
1279
|
-
a: int
|
|
1280
|
-
|
|
1281
|
-
_created_event_class = Opened
|
|
1282
|
-
|
|
1283
|
-
@event(Opened)
|
|
1284
|
-
def __init__(self):
|
|
1285
|
-
pass
|
|
1286
|
-
|
|
1287
|
-
self.assertEqual(
|
|
1288
|
-
cm.exception.args[0],
|
|
1289
|
-
"Can't use both '_created_event_class' and decorator on __init__",
|
|
1290
|
-
)
|
|
1291
|
-
|
|
1292
|
-
def test_raises_when_using_created_event_name_and_init_event_decorator(self):
|
|
1293
|
-
# Different name.
|
|
1294
|
-
with self.assertRaises(TypeError) as cm:
|
|
1295
|
-
|
|
1296
|
-
class _(Aggregate, created_event_name="Opened"): # noqa: N801
|
|
1297
|
-
class Started(AggregateCreated):
|
|
1298
|
-
a: int
|
|
1299
|
-
|
|
1300
|
-
class Opened(AggregateCreated):
|
|
1301
|
-
a: int
|
|
1302
|
-
|
|
1303
|
-
@event("Started")
|
|
1304
|
-
def __init__(self):
|
|
1305
|
-
pass
|
|
1306
|
-
|
|
1307
|
-
self.assertEqual(
|
|
1308
|
-
"Can't use both 'created_event_name' and decorator on __init__",
|
|
1309
|
-
cm.exception.args[0],
|
|
1310
|
-
)
|
|
1311
|
-
|
|
1312
|
-
# Same name.
|
|
1313
|
-
with self.assertRaises(TypeError) as cm:
|
|
1314
|
-
|
|
1315
|
-
class _(Aggregate, created_event_name="Opened"): # noqa: N801
|
|
1316
|
-
class Started(AggregateCreated):
|
|
1317
|
-
a: int
|
|
1318
|
-
|
|
1319
|
-
class Opened(AggregateCreated):
|
|
1320
|
-
a: int
|
|
1321
|
-
|
|
1322
|
-
@event("Opened")
|
|
1323
|
-
def __init__(self):
|
|
1324
|
-
pass
|
|
1325
|
-
|
|
1326
|
-
self.assertEqual(
|
|
1327
|
-
cm.exception.args[0],
|
|
1328
|
-
"Can't use both 'created_event_name' and decorator on __init__",
|
|
1329
|
-
)
|
|
1330
|
-
|
|
1331
|
-
# Different class.
|
|
1332
|
-
with self.assertRaises(TypeError) as cm:
|
|
1333
|
-
|
|
1334
|
-
class _(Aggregate, created_event_name="Opened"): # noqa: N801
|
|
1335
|
-
class Started(AggregateCreated):
|
|
1336
|
-
a: int
|
|
1337
|
-
|
|
1338
|
-
class Opened(AggregateCreated):
|
|
1339
|
-
a: int
|
|
1340
|
-
|
|
1341
|
-
@event(Started)
|
|
1342
|
-
def __init__(self):
|
|
1343
|
-
pass
|
|
1344
|
-
|
|
1345
|
-
self.assertEqual(
|
|
1346
|
-
cm.exception.args[0],
|
|
1347
|
-
"Can't use both 'created_event_name' and decorator on __init__",
|
|
1348
|
-
)
|
|
1349
|
-
|
|
1350
|
-
# Same class.
|
|
1351
|
-
with self.assertRaises(TypeError) as cm:
|
|
1352
|
-
|
|
1353
|
-
class _(Aggregate, created_event_name="Opened"): # noqa: N801
|
|
1354
|
-
class Started(AggregateCreated):
|
|
1355
|
-
a: int
|
|
1356
|
-
|
|
1357
|
-
class Opened(AggregateCreated):
|
|
1358
|
-
a: int
|
|
1359
|
-
|
|
1360
|
-
@event(Opened)
|
|
1361
|
-
def __init__(self):
|
|
1362
|
-
pass
|
|
1363
|
-
|
|
1364
|
-
self.assertEqual(
|
|
1365
|
-
cm.exception.args[0],
|
|
1366
|
-
"Can't use both 'created_event_name' and decorator on __init__",
|
|
1367
|
-
)
|
|
1368
|
-
|
|
1369
|
-
def test_raises_when_using_init_event_decorator_without_args(self):
|
|
1370
|
-
# Different name.
|
|
1371
|
-
with self.assertRaises(TypeError) as cm:
|
|
1372
|
-
|
|
1373
|
-
class _(Aggregate): # noqa: N801
|
|
1374
|
-
@event
|
|
1375
|
-
def __init__(self):
|
|
1376
|
-
pass
|
|
1377
|
-
|
|
1378
|
-
self.assertEqual(
|
|
1379
|
-
"Decorator on __init__ has neither event name nor class",
|
|
1380
|
-
cm.exception.args[0],
|
|
1381
|
-
)
|
|
1382
|
-
|
|
1383
|
-
def test_raises_type_error_if_given_event_class_cannot_init_aggregate(self):
|
|
1384
|
-
with self.assertRaises(TypeError) as cm:
|
|
1385
|
-
|
|
1386
|
-
class MyAggregate(Aggregate):
|
|
1387
|
-
@event(Aggregate.Event)
|
|
1388
|
-
def __init__(self):
|
|
1389
|
-
pass
|
|
1390
|
-
|
|
1391
|
-
self.assertIn("not subclass of CanInit", cm.exception.args[0])
|
|
1392
|
-
|
|
1393
|
-
def test_raises_if_given_event_class_on_command_method_can_init_aggregate(self):
|
|
1394
|
-
with self.assertRaises(TypeError) as cm:
|
|
1395
|
-
|
|
1396
|
-
class MyAggregate(Aggregate):
|
|
1397
|
-
@event(Aggregate.Created)
|
|
1398
|
-
def do_something(self):
|
|
1399
|
-
pass
|
|
1400
|
-
|
|
1401
|
-
self.assertIn("is subclass of CanInit", cm.exception.args[0])
|
|
1402
|
-
|
|
1403
|
-
def test_raises_if_given_event_class_on_command_method_is_not_aggregate_event(self):
|
|
1404
|
-
with self.assertRaises(TypeError) as cm:
|
|
1405
|
-
|
|
1406
|
-
class X:
|
|
1407
|
-
pass
|
|
1408
|
-
|
|
1409
|
-
class MyAggregate(Aggregate):
|
|
1410
|
-
@event(X)
|
|
1411
|
-
def do_something(self):
|
|
1412
|
-
pass
|
|
1413
|
-
|
|
1414
|
-
self.assertIn(
|
|
1415
|
-
"is not a str, function, property, or subclass of CanMutateAggregate",
|
|
1416
|
-
cm.exception.args[0],
|
|
1417
|
-
)
|
|
1418
|
-
|
|
1419
|
-
def test_decorated_method_has_original_docstring(self):
|
|
1420
|
-
class MyAggregate(Aggregate):
|
|
1421
|
-
def method0(self):
|
|
1422
|
-
"""Method 0"""
|
|
1423
|
-
|
|
1424
|
-
@event
|
|
1425
|
-
def method1(self):
|
|
1426
|
-
"""Method 1"""
|
|
1427
|
-
|
|
1428
|
-
self.assertEqual(MyAggregate.method0.__doc__, "Method 0")
|
|
1429
|
-
self.assertEqual(MyAggregate().method0.__doc__, "Method 0")
|
|
1430
|
-
self.assertEqual(MyAggregate.method1.__doc__, "Method 1")
|
|
1431
|
-
self.assertEqual(MyAggregate().method1.__doc__, "Method 1")
|
|
1432
|
-
|
|
1433
|
-
def test_decorated_method_has_original_annotations(self):
|
|
1434
|
-
class MyAggregate(Aggregate):
|
|
1435
|
-
def method0(self, a: int):
|
|
1436
|
-
"""Method 0"""
|
|
1437
|
-
|
|
1438
|
-
@event
|
|
1439
|
-
def method1(self, a: int):
|
|
1440
|
-
"""Method 1"""
|
|
1441
|
-
|
|
1442
|
-
expected_annotations = {"a": int}
|
|
1443
|
-
self.assertEqual(MyAggregate.method0.__annotations__, expected_annotations)
|
|
1444
|
-
self.assertEqual(MyAggregate().method0.__annotations__, expected_annotations)
|
|
1445
|
-
self.assertEqual(MyAggregate.method1.__annotations__, expected_annotations)
|
|
1446
|
-
self.assertEqual(MyAggregate().method1.__annotations__, expected_annotations)
|
|
1447
|
-
|
|
1448
|
-
def test_decorated_method_has_original_module(self):
|
|
1449
|
-
class MyAggregate(Aggregate):
|
|
1450
|
-
def method0(self, a: int):
|
|
1451
|
-
"""Method 0"""
|
|
1452
|
-
|
|
1453
|
-
@event
|
|
1454
|
-
def method1(self, a: int):
|
|
1455
|
-
"""Method 1"""
|
|
1456
|
-
|
|
1457
|
-
expected_module = __name__
|
|
1458
|
-
self.assertEqual(MyAggregate.method0.__module__, expected_module)
|
|
1459
|
-
self.assertEqual(MyAggregate().method0.__module__, expected_module)
|
|
1460
|
-
self.assertEqual(MyAggregate.method1.__module__, expected_module)
|
|
1461
|
-
self.assertEqual(MyAggregate().method1.__module__, expected_module)
|
|
1462
|
-
|
|
1463
|
-
def test_decorated_method_has_original_name(self):
|
|
1464
|
-
class MyAggregate(Aggregate):
|
|
1465
|
-
def method0(self, a: int):
|
|
1466
|
-
"""Method 0"""
|
|
1467
|
-
|
|
1468
|
-
@event
|
|
1469
|
-
def method1(self, a: int):
|
|
1470
|
-
"""Method 1"""
|
|
1471
|
-
|
|
1472
|
-
self.assertEqual(MyAggregate.method0.__name__, "method0")
|
|
1473
|
-
self.assertEqual(MyAggregate().method0.__name__, "method0")
|
|
1474
|
-
self.assertEqual(MyAggregate.method1.__name__, "method1")
|
|
1475
|
-
self.assertEqual(MyAggregate().method1.__name__, "method1")
|
|
1476
|
-
|
|
1477
|
-
# def test_raises_when_apply_method_returns_value(self):
|
|
1478
|
-
# # Different name.
|
|
1479
|
-
# class MyAgg(Aggregate):
|
|
1480
|
-
# @event("EventName")
|
|
1481
|
-
# def name(self):
|
|
1482
|
-
# return 1
|
|
1483
|
-
#
|
|
1484
|
-
# a = MyAgg()
|
|
1485
|
-
#
|
|
1486
|
-
# with self.assertRaises(TypeError) as cm:
|
|
1487
|
-
# a.name()
|
|
1488
|
-
# msg = str(cm.exception.args[0])
|
|
1489
|
-
# self.assertTrue(msg.startswith("Unexpected value returned from "), msg)
|
|
1490
|
-
# self.assertTrue(
|
|
1491
|
-
# msg.endswith(
|
|
1492
|
-
# "MyAgg.name(). Values returned from 'apply' methods are discarded."
|
|
1493
|
-
# ),
|
|
1494
|
-
# msg,
|
|
1495
|
-
# )
|
|
1496
|
-
def test_can_include_timestamp_in_command_method_signature(self):
|
|
1497
|
-
class Order(Aggregate):
|
|
1498
|
-
def __init__(self, name, timestamp=None):
|
|
1499
|
-
self.name = name
|
|
1500
|
-
self.confirmed_at = None
|
|
1501
|
-
self.pickedup_at = None
|
|
1502
|
-
|
|
1503
|
-
class Started(AggregateCreated):
|
|
1504
|
-
name: str
|
|
1505
|
-
|
|
1506
|
-
@event("Confirmed")
|
|
1507
|
-
def confirm(self, timestamp=None):
|
|
1508
|
-
self.confirmed_at = timestamp
|
|
1509
|
-
|
|
1510
|
-
class PickedUp(Aggregate.Event):
|
|
1511
|
-
pass
|
|
1512
|
-
|
|
1513
|
-
@event(PickedUp)
|
|
1514
|
-
def picked_up(self, timestamp=None):
|
|
1515
|
-
self.pickedup_at = timestamp
|
|
1516
|
-
|
|
1517
|
-
order1 = Order("order1")
|
|
1518
|
-
self.assertIsInstance(order1.created_on, datetime)
|
|
1519
|
-
order1.confirm()
|
|
1520
|
-
self.assertIsInstance(order1.modified_on, datetime)
|
|
1521
|
-
self.assertGreater(order1.modified_on, order1.created_on)
|
|
1522
|
-
|
|
1523
|
-
order2 = Order(
|
|
1524
|
-
"order2", timestamp=datetime(year=2000, month=1, day=1, tzinfo=timezone.utc)
|
|
1525
|
-
)
|
|
1526
|
-
self.assertIsInstance(order2.created_on, datetime)
|
|
1527
|
-
self.assertEqual(order2.created_on.year, 2000)
|
|
1528
|
-
self.assertEqual(order2.created_on.month, 1)
|
|
1529
|
-
self.assertEqual(order2.created_on.day, 1)
|
|
1530
|
-
|
|
1531
|
-
order2.confirm(
|
|
1532
|
-
timestamp=datetime(year=2000, month=1, day=2, tzinfo=timezone.utc)
|
|
1533
|
-
)
|
|
1534
|
-
self.assertIsInstance(order2.created_on, datetime)
|
|
1535
|
-
self.assertEqual(order2.modified_on.year, 2000)
|
|
1536
|
-
self.assertEqual(order2.modified_on.month, 1)
|
|
1537
|
-
self.assertEqual(order2.modified_on.day, 2)
|
|
1538
|
-
self.assertEqual(order2.confirmed_at, order2.modified_on)
|
|
1539
|
-
|
|
1540
|
-
order2.picked_up(
|
|
1541
|
-
timestamp=datetime(year=2000, month=1, day=3, tzinfo=timezone.utc)
|
|
1542
|
-
)
|
|
1543
|
-
self.assertIsInstance(order2.created_on, datetime)
|
|
1544
|
-
self.assertEqual(order2.modified_on.year, 2000)
|
|
1545
|
-
self.assertEqual(order2.modified_on.month, 1)
|
|
1546
|
-
self.assertEqual(order2.modified_on.day, 3)
|
|
1547
|
-
self.assertEqual(order2.pickedup_at, order2.modified_on)
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
class TestOrder(TestCase):
|
|
1551
|
-
def test(self) -> None:
|
|
1552
|
-
class Order(Aggregate):
|
|
1553
|
-
def __init__(self, name):
|
|
1554
|
-
self.name = name
|
|
1555
|
-
self.confirmed_at = None
|
|
1556
|
-
self.pickedup_at = None
|
|
1557
|
-
|
|
1558
|
-
class Started(AggregateCreated):
|
|
1559
|
-
name: str
|
|
1560
|
-
|
|
1561
|
-
@event("Confirmed")
|
|
1562
|
-
def confirm(self, at):
|
|
1563
|
-
self.confirmed_at = at
|
|
1564
|
-
|
|
1565
|
-
def pickup(self, at):
|
|
1566
|
-
if self.confirmed_at:
|
|
1567
|
-
self._pickup(at)
|
|
1568
|
-
else:
|
|
1569
|
-
msg = "Order is not confirmed"
|
|
1570
|
-
raise Exception(msg)
|
|
1571
|
-
|
|
1572
|
-
@event("Pickedup")
|
|
1573
|
-
def _pickup(self, at):
|
|
1574
|
-
self.pickedup_at = at
|
|
1575
|
-
|
|
1576
|
-
order = Order("my order")
|
|
1577
|
-
self.assertEqual(order.name, "my order")
|
|
1578
|
-
|
|
1579
|
-
with self.assertRaises(Exception) as cm:
|
|
1580
|
-
order.pickup(AggregateEvent.create_timestamp())
|
|
1581
|
-
self.assertEqual(cm.exception.args[0], "Order is not confirmed")
|
|
1582
|
-
|
|
1583
|
-
self.assertEqual(order.confirmed_at, None)
|
|
1584
|
-
self.assertEqual(order.pickedup_at, None)
|
|
1585
|
-
|
|
1586
|
-
order.confirm(AggregateEvent.create_timestamp())
|
|
1587
|
-
self.assertIsInstance(order.confirmed_at, datetime)
|
|
1588
|
-
self.assertEqual(order.pickedup_at, None)
|
|
1589
|
-
|
|
1590
|
-
order.pickup(AggregateEvent.create_timestamp())
|
|
1591
|
-
self.assertIsInstance(order.confirmed_at, datetime)
|
|
1592
|
-
self.assertIsInstance(order.pickedup_at, datetime)
|
|
1593
|
-
|
|
1594
|
-
# Check the events determine the state correctly.
|
|
1595
|
-
pending_events = order.collect_events()
|
|
1596
|
-
copy = None
|
|
1597
|
-
for e in pending_events:
|
|
1598
|
-
copy = e.mutate(copy)
|
|
1599
|
-
|
|
1600
|
-
self.assertEqual(copy.name, order.name)
|
|
1601
|
-
self.assertEqual(copy.created_on, order.created_on)
|
|
1602
|
-
self.assertEqual(copy.modified_on, order.modified_on)
|
|
1603
|
-
self.assertEqual(copy.confirmed_at, order.confirmed_at)
|
|
1604
|
-
self.assertEqual(copy.pickedup_at, order.pickedup_at)
|