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.

Files changed (129) hide show
  1. eventsourcing/postgres.py +2 -1
  2. eventsourcing/system.py +3 -1
  3. {eventsourcing-9.3.2.dist-info → eventsourcing-9.3.4.dist-info}/METADATA +3 -8
  4. eventsourcing-9.3.4.dist-info/RECORD +24 -0
  5. eventsourcing/examples/__init__.py +0 -0
  6. eventsourcing/examples/aggregate1/__init__.py +0 -0
  7. eventsourcing/examples/aggregate1/application.py +0 -27
  8. eventsourcing/examples/aggregate1/domainmodel.py +0 -16
  9. eventsourcing/examples/aggregate1/test_application.py +0 -37
  10. eventsourcing/examples/aggregate2/__init__.py +0 -0
  11. eventsourcing/examples/aggregate2/application.py +0 -27
  12. eventsourcing/examples/aggregate2/domainmodel.py +0 -22
  13. eventsourcing/examples/aggregate2/test_application.py +0 -37
  14. eventsourcing/examples/aggregate3/__init__.py +0 -0
  15. eventsourcing/examples/aggregate3/application.py +0 -27
  16. eventsourcing/examples/aggregate3/domainmodel.py +0 -38
  17. eventsourcing/examples/aggregate3/test_application.py +0 -37
  18. eventsourcing/examples/aggregate4/__init__.py +0 -0
  19. eventsourcing/examples/aggregate4/application.py +0 -27
  20. eventsourcing/examples/aggregate4/domainmodel.py +0 -114
  21. eventsourcing/examples/aggregate4/test_application.py +0 -38
  22. eventsourcing/examples/aggregate5/__init__.py +0 -0
  23. eventsourcing/examples/aggregate5/application.py +0 -27
  24. eventsourcing/examples/aggregate5/domainmodel.py +0 -131
  25. eventsourcing/examples/aggregate5/test_application.py +0 -38
  26. eventsourcing/examples/aggregate6/__init__.py +0 -0
  27. eventsourcing/examples/aggregate6/application.py +0 -30
  28. eventsourcing/examples/aggregate6/domainmodel.py +0 -123
  29. eventsourcing/examples/aggregate6/test_application.py +0 -38
  30. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  31. eventsourcing/examples/aggregate6a/application.py +0 -40
  32. eventsourcing/examples/aggregate6a/domainmodel.py +0 -149
  33. eventsourcing/examples/aggregate6a/test_application.py +0 -45
  34. eventsourcing/examples/aggregate7/__init__.py +0 -0
  35. eventsourcing/examples/aggregate7/application.py +0 -48
  36. eventsourcing/examples/aggregate7/domainmodel.py +0 -144
  37. eventsourcing/examples/aggregate7/persistence.py +0 -57
  38. eventsourcing/examples/aggregate7/test_application.py +0 -38
  39. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -45
  40. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -67
  41. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  42. eventsourcing/examples/aggregate7a/application.py +0 -56
  43. eventsourcing/examples/aggregate7a/domainmodel.py +0 -170
  44. eventsourcing/examples/aggregate7a/test_application.py +0 -46
  45. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -45
  46. eventsourcing/examples/aggregate8/__init__.py +0 -0
  47. eventsourcing/examples/aggregate8/application.py +0 -47
  48. eventsourcing/examples/aggregate8/domainmodel.py +0 -65
  49. eventsourcing/examples/aggregate8/persistence.py +0 -57
  50. eventsourcing/examples/aggregate8/test_application.py +0 -37
  51. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -44
  52. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -38
  53. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  54. eventsourcing/examples/bankaccounts/application.py +0 -70
  55. eventsourcing/examples/bankaccounts/domainmodel.py +0 -56
  56. eventsourcing/examples/bankaccounts/test.py +0 -173
  57. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  58. eventsourcing/examples/cargoshipping/application.py +0 -126
  59. eventsourcing/examples/cargoshipping/domainmodel.py +0 -330
  60. eventsourcing/examples/cargoshipping/interface.py +0 -143
  61. eventsourcing/examples/cargoshipping/test.py +0 -231
  62. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  63. eventsourcing/examples/contentmanagement/application.py +0 -118
  64. eventsourcing/examples/contentmanagement/domainmodel.py +0 -69
  65. eventsourcing/examples/contentmanagement/test.py +0 -180
  66. eventsourcing/examples/contentmanagement/utils.py +0 -26
  67. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  68. eventsourcing/examples/contentmanagementsystem/application.py +0 -54
  69. eventsourcing/examples/contentmanagementsystem/postgres.py +0 -17
  70. eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -17
  71. eventsourcing/examples/contentmanagementsystem/system.py +0 -14
  72. eventsourcing/examples/contentmanagementsystem/test_system.py +0 -180
  73. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  74. eventsourcing/examples/searchablecontent/application.py +0 -45
  75. eventsourcing/examples/searchablecontent/persistence.py +0 -23
  76. eventsourcing/examples/searchablecontent/postgres.py +0 -118
  77. eventsourcing/examples/searchablecontent/sqlite.py +0 -136
  78. eventsourcing/examples/searchablecontent/test_application.py +0 -110
  79. eventsourcing/examples/searchablecontent/test_recorder.py +0 -68
  80. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  81. eventsourcing/examples/searchabletimestamps/application.py +0 -32
  82. eventsourcing/examples/searchabletimestamps/persistence.py +0 -20
  83. eventsourcing/examples/searchabletimestamps/postgres.py +0 -110
  84. eventsourcing/examples/searchabletimestamps/sqlite.py +0 -99
  85. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +0 -94
  86. eventsourcing/examples/test_invoice.py +0 -176
  87. eventsourcing/examples/test_parking_lot.py +0 -206
  88. eventsourcing/tests/application_tests/__init__.py +0 -0
  89. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -55
  90. eventsourcing/tests/application_tests/test_application_with_popo.py +0 -22
  91. eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -75
  92. eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -72
  93. eventsourcing/tests/application_tests/test_cache.py +0 -134
  94. eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -162
  95. eventsourcing/tests/application_tests/test_notificationlog.py +0 -232
  96. eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -126
  97. eventsourcing/tests/application_tests/test_processapplication.py +0 -110
  98. eventsourcing/tests/application_tests/test_processingpolicy.py +0 -109
  99. eventsourcing/tests/application_tests/test_repository.py +0 -504
  100. eventsourcing/tests/application_tests/test_snapshotting.py +0 -68
  101. eventsourcing/tests/application_tests/test_upcasting.py +0 -459
  102. eventsourcing/tests/docs_tests/__init__.py +0 -0
  103. eventsourcing/tests/docs_tests/test_docs.py +0 -293
  104. eventsourcing/tests/domain_tests/__init__.py +0 -0
  105. eventsourcing/tests/domain_tests/test_aggregate.py +0 -1200
  106. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -1604
  107. eventsourcing/tests/domain_tests/test_domainevent.py +0 -80
  108. eventsourcing/tests/interface_tests/__init__.py +0 -0
  109. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -258
  110. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  111. eventsourcing/tests/persistence_tests/test_aes.py +0 -93
  112. eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -722
  113. eventsourcing/tests/persistence_tests/test_eventstore.py +0 -72
  114. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -21
  115. eventsourcing/tests/persistence_tests/test_mapper.py +0 -113
  116. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -69
  117. eventsourcing/tests/persistence_tests/test_popo.py +0 -124
  118. eventsourcing/tests/persistence_tests/test_postgres.py +0 -1119
  119. eventsourcing/tests/persistence_tests/test_sqlite.py +0 -348
  120. eventsourcing/tests/persistence_tests/test_transcoder.py +0 -44
  121. eventsourcing/tests/system_tests/__init__.py +0 -0
  122. eventsourcing/tests/system_tests/test_runner.py +0 -935
  123. eventsourcing/tests/system_tests/test_system.py +0 -284
  124. eventsourcing/tests/utils_tests/__init__.py +0 -0
  125. eventsourcing/tests/utils_tests/test_utils.py +0 -226
  126. eventsourcing-9.3.2.dist-info/RECORD +0 -145
  127. {eventsourcing-9.3.2.dist-info → eventsourcing-9.3.4.dist-info}/AUTHORS +0 -0
  128. {eventsourcing-9.3.2.dist-info → eventsourcing-9.3.4.dist-info}/LICENSE +0 -0
  129. {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)