eventsourcing 9.4.0b2__py3-none-any.whl → 9.4.0b3__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/application.py +53 -104
- eventsourcing/cipher.py +3 -8
- eventsourcing/compressor.py +2 -6
- eventsourcing/cryptography.py +3 -8
- eventsourcing/dispatch.py +2 -2
- eventsourcing/domain.py +211 -291
- eventsourcing/interface.py +10 -24
- eventsourcing/persistence.py +91 -212
- eventsourcing/popo.py +2 -2
- eventsourcing/postgres.py +7 -10
- eventsourcing/projection.py +17 -40
- eventsourcing/sqlite.py +4 -7
- eventsourcing/system.py +89 -156
- eventsourcing/tests/application.py +14 -10
- eventsourcing/tests/domain.py +14 -34
- eventsourcing/tests/persistence.py +21 -18
- eventsourcing/utils.py +11 -17
- {eventsourcing-9.4.0b2.dist-info → eventsourcing-9.4.0b3.dist-info}/METADATA +1 -1
- eventsourcing-9.4.0b3.dist-info/RECORD +26 -0
- eventsourcing-9.4.0b2.dist-info/RECORD +0 -26
- {eventsourcing-9.4.0b2.dist-info → eventsourcing-9.4.0b3.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.4.0b2.dist-info → eventsourcing-9.4.0b3.dist-info}/LICENSE +0 -0
- {eventsourcing-9.4.0b2.dist-info → eventsourcing-9.4.0b3.dist-info}/WHEEL +0 -0
eventsourcing/persistence.py
CHANGED
|
@@ -31,9 +31,7 @@ if TYPE_CHECKING:
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class Transcoding(ABC):
|
|
34
|
-
"""
|
|
35
|
-
Abstract base class for custom transcodings.
|
|
36
|
-
"""
|
|
34
|
+
"""Abstract base class for custom transcodings."""
|
|
37
35
|
|
|
38
36
|
type: type
|
|
39
37
|
name: str
|
|
@@ -48,9 +46,7 @@ class Transcoding(ABC):
|
|
|
48
46
|
|
|
49
47
|
|
|
50
48
|
class Transcoder(ABC):
|
|
51
|
-
"""
|
|
52
|
-
Abstract base class for transcoders.
|
|
53
|
-
"""
|
|
49
|
+
"""Abstract base class for transcoders."""
|
|
54
50
|
|
|
55
51
|
@abstractmethod
|
|
56
52
|
def encode(self, obj: Any) -> bytes:
|
|
@@ -62,15 +58,11 @@ class Transcoder(ABC):
|
|
|
62
58
|
|
|
63
59
|
|
|
64
60
|
class TranscodingNotRegisteredError(EventSourcingError, TypeError):
|
|
65
|
-
"""
|
|
66
|
-
Raised when a transcoding isn't registered with JSONTranscoder.
|
|
67
|
-
"""
|
|
61
|
+
"""Raised when a transcoding isn't registered with JSONTranscoder."""
|
|
68
62
|
|
|
69
63
|
|
|
70
64
|
class JSONTranscoder(Transcoder):
|
|
71
|
-
"""
|
|
72
|
-
Extensible transcoder that uses the Python :mod:`json` module.
|
|
73
|
-
"""
|
|
65
|
+
"""Extensible transcoder that uses the Python :mod:`json` module."""
|
|
74
66
|
|
|
75
67
|
def __init__(self) -> None:
|
|
76
68
|
self.types: dict[type, Transcoding] = {}
|
|
@@ -83,22 +75,16 @@ class JSONTranscoder(Transcoder):
|
|
|
83
75
|
self.decoder = json.JSONDecoder(object_hook=self._decode_obj)
|
|
84
76
|
|
|
85
77
|
def register(self, transcoding: Transcoding) -> None:
|
|
86
|
-
"""
|
|
87
|
-
Registers given transcoding with the transcoder.
|
|
88
|
-
"""
|
|
78
|
+
"""Registers given transcoding with the transcoder."""
|
|
89
79
|
self.types[transcoding.type] = transcoding
|
|
90
80
|
self.names[transcoding.name] = transcoding
|
|
91
81
|
|
|
92
82
|
def encode(self, obj: Any) -> bytes:
|
|
93
|
-
"""
|
|
94
|
-
Encodes given object as a bytes array.
|
|
95
|
-
"""
|
|
83
|
+
"""Encodes given object as a bytes array."""
|
|
96
84
|
return self.encoder.encode(obj).encode("utf8")
|
|
97
85
|
|
|
98
86
|
def decode(self, data: bytes) -> Any:
|
|
99
|
-
"""
|
|
100
|
-
Decodes bytes array as previously encoded object.
|
|
101
|
-
"""
|
|
87
|
+
"""Decodes bytes array as previously encoded object."""
|
|
102
88
|
return self.decoder.decode(data.decode("utf8"))
|
|
103
89
|
|
|
104
90
|
def _encode_obj(self, o: Any) -> dict[str, Any]:
|
|
@@ -130,10 +116,10 @@ class JSONTranscoder(Transcoder):
|
|
|
130
116
|
return d
|
|
131
117
|
else:
|
|
132
118
|
try:
|
|
133
|
-
transcoding = self.names[cast(str, _type_)]
|
|
119
|
+
transcoding = self.names[cast("str", _type_)]
|
|
134
120
|
except KeyError as e:
|
|
135
121
|
msg = (
|
|
136
|
-
f"Data serialized with name '{cast(str, _type_)}' is not "
|
|
122
|
+
f"Data serialized with name '{cast('str', _type_)}' is not "
|
|
137
123
|
"deserializable. Please register a "
|
|
138
124
|
"custom transcoding for this type."
|
|
139
125
|
)
|
|
@@ -145,9 +131,7 @@ class JSONTranscoder(Transcoder):
|
|
|
145
131
|
|
|
146
132
|
|
|
147
133
|
class UUIDAsHex(Transcoding):
|
|
148
|
-
"""
|
|
149
|
-
Transcoding that represents :class:`UUID` objects as hex values.
|
|
150
|
-
"""
|
|
134
|
+
"""Transcoding that represents :class:`UUID` objects as hex values."""
|
|
151
135
|
|
|
152
136
|
type = UUID
|
|
153
137
|
name = "uuid_hex"
|
|
@@ -161,9 +145,7 @@ class UUIDAsHex(Transcoding):
|
|
|
161
145
|
|
|
162
146
|
|
|
163
147
|
class DecimalAsStr(Transcoding):
|
|
164
|
-
"""
|
|
165
|
-
Transcoding that represents :class:`Decimal` objects as strings.
|
|
166
|
-
"""
|
|
148
|
+
"""Transcoding that represents :class:`Decimal` objects as strings."""
|
|
167
149
|
|
|
168
150
|
type = Decimal
|
|
169
151
|
name = "decimal_str"
|
|
@@ -176,9 +158,7 @@ class DecimalAsStr(Transcoding):
|
|
|
176
158
|
|
|
177
159
|
|
|
178
160
|
class DatetimeAsISO(Transcoding):
|
|
179
|
-
"""
|
|
180
|
-
Transcoding that represents :class:`datetime` objects as ISO strings.
|
|
181
|
-
"""
|
|
161
|
+
"""Transcoding that represents :class:`datetime` objects as ISO strings."""
|
|
182
162
|
|
|
183
163
|
type = datetime
|
|
184
164
|
name = "datetime_iso"
|
|
@@ -193,8 +173,7 @@ class DatetimeAsISO(Transcoding):
|
|
|
193
173
|
|
|
194
174
|
@dataclass(frozen=True)
|
|
195
175
|
class StoredEvent:
|
|
196
|
-
"""
|
|
197
|
-
Frozen dataclass that represents :class:`~eventsourcing.domain.DomainEvent`
|
|
176
|
+
"""Frozen dataclass that represents :class:`~eventsourcing.domain.DomainEvent`
|
|
198
177
|
objects, such as aggregate :class:`~eventsourcing.domain.Aggregate.Event`
|
|
199
178
|
objects and :class:`~eventsourcing.domain.Snapshot` objects.
|
|
200
179
|
"""
|
|
@@ -210,56 +189,39 @@ class StoredEvent:
|
|
|
210
189
|
|
|
211
190
|
|
|
212
191
|
class Compressor(ABC):
|
|
213
|
-
"""
|
|
214
|
-
Base class for compressors.
|
|
215
|
-
"""
|
|
192
|
+
"""Base class for compressors."""
|
|
216
193
|
|
|
217
194
|
@abstractmethod
|
|
218
195
|
def compress(self, data: bytes) -> bytes:
|
|
219
|
-
"""
|
|
220
|
-
Compress bytes.
|
|
221
|
-
"""
|
|
196
|
+
"""Compress bytes."""
|
|
222
197
|
|
|
223
198
|
@abstractmethod
|
|
224
199
|
def decompress(self, data: bytes) -> bytes:
|
|
225
|
-
"""
|
|
226
|
-
Decompress bytes.
|
|
227
|
-
"""
|
|
200
|
+
"""Decompress bytes."""
|
|
228
201
|
|
|
229
202
|
|
|
230
203
|
class Cipher(ABC):
|
|
231
|
-
"""
|
|
232
|
-
Base class for ciphers.
|
|
233
|
-
"""
|
|
204
|
+
"""Base class for ciphers."""
|
|
234
205
|
|
|
235
206
|
@abstractmethod
|
|
236
207
|
def __init__(self, environment: Environment):
|
|
237
|
-
"""
|
|
238
|
-
Initialises cipher with given environment.
|
|
239
|
-
"""
|
|
208
|
+
"""Initialises cipher with given environment."""
|
|
240
209
|
|
|
241
210
|
@abstractmethod
|
|
242
211
|
def encrypt(self, plaintext: bytes) -> bytes:
|
|
243
|
-
"""
|
|
244
|
-
Return ciphertext for given plaintext.
|
|
245
|
-
"""
|
|
212
|
+
"""Return ciphertext for given plaintext."""
|
|
246
213
|
|
|
247
214
|
@abstractmethod
|
|
248
215
|
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
249
|
-
"""
|
|
250
|
-
Return plaintext for given ciphertext.
|
|
251
|
-
"""
|
|
216
|
+
"""Return plaintext for given ciphertext."""
|
|
252
217
|
|
|
253
218
|
|
|
254
219
|
class MapperDeserialisationError(EventSourcingError, ValueError):
|
|
255
|
-
"""
|
|
256
|
-
Raised when deserialization fails in a Mapper.
|
|
257
|
-
"""
|
|
220
|
+
"""Raised when deserialization fails in a Mapper."""
|
|
258
221
|
|
|
259
222
|
|
|
260
223
|
class Mapper:
|
|
261
|
-
"""
|
|
262
|
-
Converts between domain event objects and :class:`StoredEvent` objects.
|
|
224
|
+
"""Converts between domain event objects and :class:`StoredEvent` objects.
|
|
263
225
|
|
|
264
226
|
Uses a :class:`Transcoder`, and optionally a cryptographic cipher and compressor.
|
|
265
227
|
"""
|
|
@@ -275,9 +237,7 @@ class Mapper:
|
|
|
275
237
|
self.cipher = cipher
|
|
276
238
|
|
|
277
239
|
def to_stored_event(self, domain_event: DomainEventProtocol) -> StoredEvent:
|
|
278
|
-
"""
|
|
279
|
-
Converts the given domain event to a :class:`StoredEvent` object.
|
|
280
|
-
"""
|
|
240
|
+
"""Converts the given domain event to a :class:`StoredEvent` object."""
|
|
281
241
|
topic = get_topic(domain_event.__class__)
|
|
282
242
|
event_state = domain_event.__dict__.copy()
|
|
283
243
|
originator_id = event_state.pop("originator_id")
|
|
@@ -298,9 +258,7 @@ class Mapper:
|
|
|
298
258
|
)
|
|
299
259
|
|
|
300
260
|
def to_domain_event(self, stored_event: StoredEvent) -> DomainEventProtocol:
|
|
301
|
-
"""
|
|
302
|
-
Converts the given :class:`StoredEvent` to a domain event object.
|
|
303
|
-
"""
|
|
261
|
+
"""Converts the given :class:`StoredEvent` to a domain event object."""
|
|
304
262
|
stored_state = stored_event.state
|
|
305
263
|
try:
|
|
306
264
|
if self.cipher:
|
|
@@ -332,42 +290,34 @@ class Mapper:
|
|
|
332
290
|
|
|
333
291
|
|
|
334
292
|
class RecordConflictError(EventSourcingError):
|
|
335
|
-
"""
|
|
336
|
-
Legacy exception, replaced with IntegrityError.
|
|
337
|
-
"""
|
|
293
|
+
"""Legacy exception, replaced with IntegrityError."""
|
|
338
294
|
|
|
339
295
|
|
|
340
296
|
class PersistenceError(EventSourcingError):
|
|
341
|
-
"""
|
|
342
|
-
The base class of the other exceptions in this module.
|
|
297
|
+
"""The base class of the other exceptions in this module.
|
|
343
298
|
|
|
344
299
|
Exception class names follow https://www.python.org/dev/peps/pep-0249/#exceptions
|
|
345
300
|
"""
|
|
346
301
|
|
|
347
302
|
|
|
348
303
|
class InterfaceError(PersistenceError):
|
|
349
|
-
"""
|
|
350
|
-
Exception raised for errors that are related to the database
|
|
304
|
+
"""Exception raised for errors that are related to the database
|
|
351
305
|
interface rather than the database itself.
|
|
352
306
|
"""
|
|
353
307
|
|
|
354
308
|
|
|
355
309
|
class DatabaseError(PersistenceError):
|
|
356
|
-
"""
|
|
357
|
-
Exception raised for errors that are related to the database.
|
|
358
|
-
"""
|
|
310
|
+
"""Exception raised for errors that are related to the database."""
|
|
359
311
|
|
|
360
312
|
|
|
361
313
|
class DataError(DatabaseError):
|
|
362
|
-
"""
|
|
363
|
-
Exception raised for errors that are due to problems with the
|
|
314
|
+
"""Exception raised for errors that are due to problems with the
|
|
364
315
|
processed data like division by zero, numeric value out of range, etc.
|
|
365
316
|
"""
|
|
366
317
|
|
|
367
318
|
|
|
368
319
|
class OperationalError(DatabaseError):
|
|
369
|
-
"""
|
|
370
|
-
Exception raised for errors that are related to the database's
|
|
320
|
+
"""Exception raised for errors that are related to the database's
|
|
371
321
|
operation and not necessarily under the control of the programmer,
|
|
372
322
|
e.g. an unexpected disconnect occurs, the data source name is not
|
|
373
323
|
found, a transaction could not be processed, a memory allocation
|
|
@@ -376,31 +326,27 @@ class OperationalError(DatabaseError):
|
|
|
376
326
|
|
|
377
327
|
|
|
378
328
|
class IntegrityError(DatabaseError, RecordConflictError):
|
|
379
|
-
"""
|
|
380
|
-
Exception raised when the relational integrity of the
|
|
329
|
+
"""Exception raised when the relational integrity of the
|
|
381
330
|
database is affected, e.g. a foreign key check fails.
|
|
382
331
|
"""
|
|
383
332
|
|
|
384
333
|
|
|
385
334
|
class InternalError(DatabaseError):
|
|
386
|
-
"""
|
|
387
|
-
Exception raised when the database encounters an internal
|
|
335
|
+
"""Exception raised when the database encounters an internal
|
|
388
336
|
error, e.g. the cursor is not valid anymore, the transaction
|
|
389
337
|
is out of sync, etc.
|
|
390
338
|
"""
|
|
391
339
|
|
|
392
340
|
|
|
393
341
|
class ProgrammingError(DatabaseError):
|
|
394
|
-
"""
|
|
395
|
-
Exception raised for database programming errors, e.g. table
|
|
342
|
+
"""Exception raised for database programming errors, e.g. table
|
|
396
343
|
not found or already exists, syntax error in the SQL statement,
|
|
397
344
|
wrong number of parameters specified, etc.
|
|
398
345
|
"""
|
|
399
346
|
|
|
400
347
|
|
|
401
348
|
class NotSupportedError(DatabaseError):
|
|
402
|
-
"""
|
|
403
|
-
Exception raised in case a method or database API was used
|
|
349
|
+
"""Exception raised in case a method or database API was used
|
|
404
350
|
which is not supported by the database, e.g. calling the
|
|
405
351
|
rollback() method on a connection that does not support
|
|
406
352
|
transaction or has transactions turned off.
|
|
@@ -408,9 +354,7 @@ class NotSupportedError(DatabaseError):
|
|
|
408
354
|
|
|
409
355
|
|
|
410
356
|
class WaitInterruptedError(PersistenceError):
|
|
411
|
-
"""
|
|
412
|
-
Raised when waiting for a tracking record is interrupted.
|
|
413
|
-
"""
|
|
357
|
+
"""Raised when waiting for a tracking record is interrupted."""
|
|
414
358
|
|
|
415
359
|
|
|
416
360
|
class Recorder:
|
|
@@ -418,17 +362,13 @@ class Recorder:
|
|
|
418
362
|
|
|
419
363
|
|
|
420
364
|
class AggregateRecorder(Recorder, ABC):
|
|
421
|
-
"""
|
|
422
|
-
Abstract base class for inserting and selecting stored events.
|
|
423
|
-
"""
|
|
365
|
+
"""Abstract base class for inserting and selecting stored events."""
|
|
424
366
|
|
|
425
367
|
@abstractmethod
|
|
426
368
|
def insert_events(
|
|
427
369
|
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
428
370
|
) -> Sequence[int] | None:
|
|
429
|
-
"""
|
|
430
|
-
Writes stored events into database.
|
|
431
|
-
"""
|
|
371
|
+
"""Writes stored events into database."""
|
|
432
372
|
|
|
433
373
|
@abstractmethod
|
|
434
374
|
def select_events(
|
|
@@ -440,24 +380,19 @@ class AggregateRecorder(Recorder, ABC):
|
|
|
440
380
|
desc: bool = False,
|
|
441
381
|
limit: int | None = None,
|
|
442
382
|
) -> list[StoredEvent]:
|
|
443
|
-
"""
|
|
444
|
-
Reads stored events from database.
|
|
445
|
-
"""
|
|
383
|
+
"""Reads stored events from database."""
|
|
446
384
|
|
|
447
385
|
|
|
448
386
|
@dataclass(frozen=True)
|
|
449
387
|
class Notification(StoredEvent):
|
|
450
|
-
"""
|
|
451
|
-
Frozen dataclass that represents domain event notifications.
|
|
452
|
-
"""
|
|
388
|
+
"""Frozen dataclass that represents domain event notifications."""
|
|
453
389
|
|
|
454
390
|
id: int
|
|
455
391
|
"""Position in an application sequence."""
|
|
456
392
|
|
|
457
393
|
|
|
458
394
|
class ApplicationRecorder(AggregateRecorder):
|
|
459
|
-
"""
|
|
460
|
-
Abstract base class for recording events in both aggregate
|
|
395
|
+
"""Abstract base class for recording events in both aggregate
|
|
461
396
|
and application sequences.
|
|
462
397
|
"""
|
|
463
398
|
|
|
@@ -471,8 +406,7 @@ class ApplicationRecorder(AggregateRecorder):
|
|
|
471
406
|
*,
|
|
472
407
|
inclusive_of_start: bool = True,
|
|
473
408
|
) -> list[Notification]:
|
|
474
|
-
"""
|
|
475
|
-
Returns a list of Notification objects representing events from an
|
|
409
|
+
"""Returns a list of Notification objects representing events from an
|
|
476
410
|
application sequence. If `inclusive_of_start` is True (the default),
|
|
477
411
|
the returned Notification objects will have IDs greater than or equal
|
|
478
412
|
to `start` and less than or equal to `stop`. If `inclusive_of_start`
|
|
@@ -482,8 +416,7 @@ class ApplicationRecorder(AggregateRecorder):
|
|
|
482
416
|
|
|
483
417
|
@abstractmethod
|
|
484
418
|
def max_notification_id(self) -> int | None:
|
|
485
|
-
"""
|
|
486
|
-
Returns the largest notification ID in an application sequence,
|
|
419
|
+
"""Returns the largest notification ID in an application sequence,
|
|
487
420
|
or None if no stored events have been recorded.
|
|
488
421
|
"""
|
|
489
422
|
|
|
@@ -491,8 +424,7 @@ class ApplicationRecorder(AggregateRecorder):
|
|
|
491
424
|
def subscribe(
|
|
492
425
|
self, gt: int | None = None, topics: Sequence[str] = ()
|
|
493
426
|
) -> Subscription[ApplicationRecorder]:
|
|
494
|
-
"""
|
|
495
|
-
Returns an iterator of Notification objects representing events from an
|
|
427
|
+
"""Returns an iterator of Notification objects representing events from an
|
|
496
428
|
application sequence.
|
|
497
429
|
|
|
498
430
|
The iterator will block after the last recorded event has been yielded, but
|
|
@@ -503,21 +435,17 @@ class ApplicationRecorder(AggregateRecorder):
|
|
|
503
435
|
|
|
504
436
|
|
|
505
437
|
class TrackingRecorder(Recorder, ABC):
|
|
506
|
-
"""
|
|
507
|
-
Abstract base class for recorders that record tracking
|
|
438
|
+
"""Abstract base class for recorders that record tracking
|
|
508
439
|
objects atomically with other state.
|
|
509
440
|
"""
|
|
510
441
|
|
|
511
442
|
@abstractmethod
|
|
512
443
|
def insert_tracking(self, tracking: Tracking) -> None:
|
|
513
|
-
"""
|
|
514
|
-
Records a tracking object.
|
|
515
|
-
"""
|
|
444
|
+
"""Records a tracking object."""
|
|
516
445
|
|
|
517
446
|
@abstractmethod
|
|
518
447
|
def max_tracking_id(self, application_name: str) -> int | None:
|
|
519
|
-
"""
|
|
520
|
-
Returns the largest notification ID across all recorded tracking objects
|
|
448
|
+
"""Returns the largest notification ID across all recorded tracking objects
|
|
521
449
|
for the named application, or None if no tracking objects have been recorded.
|
|
522
450
|
"""
|
|
523
451
|
|
|
@@ -525,8 +453,7 @@ class TrackingRecorder(Recorder, ABC):
|
|
|
525
453
|
def has_tracking_id(
|
|
526
454
|
self, application_name: str, notification_id: int | None
|
|
527
455
|
) -> bool:
|
|
528
|
-
"""
|
|
529
|
-
Returns True if a tracking object with the given application name
|
|
456
|
+
"""Returns True if a tracking object with the given application name
|
|
530
457
|
and notification ID has been recorded, and True if given notification_id is
|
|
531
458
|
None, otherwise returns False.
|
|
532
459
|
"""
|
|
@@ -538,8 +465,7 @@ class TrackingRecorder(Recorder, ABC):
|
|
|
538
465
|
timeout: float = 1.0,
|
|
539
466
|
interrupt: Event | None = None,
|
|
540
467
|
) -> None:
|
|
541
|
-
"""
|
|
542
|
-
Block until a tracking object with the given application name and a
|
|
468
|
+
"""Block until a tracking object with the given application name and a
|
|
543
469
|
notification ID greater than equal to the given value has been recorded.
|
|
544
470
|
|
|
545
471
|
Polls max_tracking_id() with exponential backoff until the timeout
|
|
@@ -594,9 +520,7 @@ class Recording:
|
|
|
594
520
|
|
|
595
521
|
|
|
596
522
|
class EventStore:
|
|
597
|
-
"""
|
|
598
|
-
Stores and retrieves domain events.
|
|
599
|
-
"""
|
|
523
|
+
"""Stores and retrieves domain events."""
|
|
600
524
|
|
|
601
525
|
def __init__(
|
|
602
526
|
self,
|
|
@@ -609,9 +533,7 @@ class EventStore:
|
|
|
609
533
|
def put(
|
|
610
534
|
self, domain_events: Sequence[DomainEventProtocol], **kwargs: Any
|
|
611
535
|
) -> list[Recording]:
|
|
612
|
-
"""
|
|
613
|
-
Stores domain events in aggregate sequence.
|
|
614
|
-
"""
|
|
536
|
+
"""Stores domain events in aggregate sequence."""
|
|
615
537
|
stored_events = list(map(self.mapper.to_stored_event, domain_events))
|
|
616
538
|
recordings = []
|
|
617
539
|
notification_ids = self.recorder.insert_events(stored_events, **kwargs)
|
|
@@ -641,9 +563,7 @@ class EventStore:
|
|
|
641
563
|
desc: bool = False,
|
|
642
564
|
limit: int | None = None,
|
|
643
565
|
) -> Iterator[DomainEventProtocol]:
|
|
644
|
-
"""
|
|
645
|
-
Retrieves domain events from aggregate sequence.
|
|
646
|
-
"""
|
|
566
|
+
"""Retrieves domain events from aggregate sequence."""
|
|
647
567
|
return map(
|
|
648
568
|
self.mapper.to_domain_event,
|
|
649
569
|
self.recorder.select_events(
|
|
@@ -661,10 +581,12 @@ TTrackingRecorder = TypeVar(
|
|
|
661
581
|
)
|
|
662
582
|
|
|
663
583
|
|
|
584
|
+
class InfrastructureFactoryError(EventSourcingError):
|
|
585
|
+
"""Raised when an infrastructure factory cannot be created."""
|
|
586
|
+
|
|
587
|
+
|
|
664
588
|
class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
665
|
-
"""
|
|
666
|
-
Abstract base class for infrastructure factories.
|
|
667
|
-
"""
|
|
589
|
+
"""Abstract base class for infrastructure factories."""
|
|
668
590
|
|
|
669
591
|
PERSISTENCE_MODULE = "PERSISTENCE_MODULE"
|
|
670
592
|
TRANSCODER_TOPIC = "TRANSCODER_TOPIC"
|
|
@@ -681,8 +603,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
681
603
|
cls: type[InfrastructureFactory[TTrackingRecorder]],
|
|
682
604
|
env: Environment | None = None,
|
|
683
605
|
) -> InfrastructureFactory[TTrackingRecorder]:
|
|
684
|
-
"""
|
|
685
|
-
Constructs concrete infrastructure factory for given
|
|
606
|
+
"""Constructs concrete infrastructure factory for given
|
|
686
607
|
named application. Reads and resolves persistence
|
|
687
608
|
topic from environment variable 'PERSISTENCE_MODULE'.
|
|
688
609
|
"""
|
|
@@ -714,7 +635,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
714
635
|
f"'{topic}' from environment "
|
|
715
636
|
f"variable '{cls.PERSISTENCE_MODULE}'"
|
|
716
637
|
)
|
|
717
|
-
raise
|
|
638
|
+
raise InfrastructureFactoryError(msg) from e
|
|
718
639
|
|
|
719
640
|
if isinstance(obj, ModuleType):
|
|
720
641
|
# Find the factory in the module.
|
|
@@ -739,26 +660,25 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
739
660
|
f"Found {len(factory_classes)} infrastructure factory classes in"
|
|
740
661
|
f" '{topic}', expected 1."
|
|
741
662
|
)
|
|
742
|
-
raise
|
|
663
|
+
raise InfrastructureFactoryError(msg)
|
|
743
664
|
elif isinstance(obj, type) and issubclass(obj, InfrastructureFactory):
|
|
744
665
|
factory_cls = obj
|
|
745
666
|
else:
|
|
746
|
-
msg =
|
|
747
|
-
|
|
667
|
+
msg = (
|
|
668
|
+
f"Topic '{topic}' didn't resolve to a persistence module "
|
|
669
|
+
f"or infrastructure factory class: {obj}"
|
|
670
|
+
)
|
|
671
|
+
raise InfrastructureFactoryError(msg)
|
|
748
672
|
return factory_cls(env=env)
|
|
749
673
|
|
|
750
674
|
def __init__(self, env: Environment):
|
|
751
|
-
"""
|
|
752
|
-
Initialises infrastructure factory object with given application name.
|
|
753
|
-
"""
|
|
675
|
+
"""Initialises infrastructure factory object with given application name."""
|
|
754
676
|
self.env = env
|
|
755
677
|
|
|
756
678
|
def transcoder(
|
|
757
679
|
self,
|
|
758
680
|
) -> Transcoder:
|
|
759
|
-
"""
|
|
760
|
-
Constructs a transcoder.
|
|
761
|
-
"""
|
|
681
|
+
"""Constructs a transcoder."""
|
|
762
682
|
transcoder_topic = self.env.get(self.TRANSCODER_TOPIC)
|
|
763
683
|
if transcoder_topic:
|
|
764
684
|
transcoder_class: type[Transcoder] = resolve_topic(transcoder_topic)
|
|
@@ -771,9 +691,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
771
691
|
transcoder: Transcoder | None = None,
|
|
772
692
|
mapper_class: type[Mapper] | None = None,
|
|
773
693
|
) -> Mapper:
|
|
774
|
-
"""
|
|
775
|
-
Constructs a mapper.
|
|
776
|
-
"""
|
|
694
|
+
"""Constructs a mapper."""
|
|
777
695
|
if mapper_class is None:
|
|
778
696
|
mapper_topic = self.env.get(self.MAPPER_TOPIC)
|
|
779
697
|
mapper_class = resolve_topic(mapper_topic) if mapper_topic else Mapper
|
|
@@ -786,8 +704,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
786
704
|
)
|
|
787
705
|
|
|
788
706
|
def cipher(self) -> Cipher | None:
|
|
789
|
-
"""
|
|
790
|
-
Reads environment variables 'CIPHER_TOPIC'
|
|
707
|
+
"""Reads environment variables 'CIPHER_TOPIC'
|
|
791
708
|
and 'CIPHER_KEY' to decide whether or not
|
|
792
709
|
to construct a cipher.
|
|
793
710
|
"""
|
|
@@ -804,8 +721,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
804
721
|
return cipher
|
|
805
722
|
|
|
806
723
|
def compressor(self) -> Compressor | None:
|
|
807
|
-
"""
|
|
808
|
-
Reads environment variable 'COMPRESSOR_TOPIC' to
|
|
724
|
+
"""Reads environment variable 'COMPRESSOR_TOPIC' to
|
|
809
725
|
decide whether or not to construct a compressor.
|
|
810
726
|
"""
|
|
811
727
|
compressor: Compressor | None = None
|
|
@@ -825,9 +741,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
825
741
|
mapper: Mapper | None = None,
|
|
826
742
|
recorder: AggregateRecorder | None = None,
|
|
827
743
|
) -> EventStore:
|
|
828
|
-
"""
|
|
829
|
-
Constructs an event store.
|
|
830
|
-
"""
|
|
744
|
+
"""Constructs an event store."""
|
|
831
745
|
return EventStore(
|
|
832
746
|
mapper=mapper or self.mapper(),
|
|
833
747
|
recorder=recorder or self.application_recorder(),
|
|
@@ -835,48 +749,36 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
|
|
|
835
749
|
|
|
836
750
|
@abstractmethod
|
|
837
751
|
def aggregate_recorder(self, purpose: str = "events") -> AggregateRecorder:
|
|
838
|
-
"""
|
|
839
|
-
Constructs an aggregate recorder.
|
|
840
|
-
"""
|
|
752
|
+
"""Constructs an aggregate recorder."""
|
|
841
753
|
|
|
842
754
|
@abstractmethod
|
|
843
755
|
def application_recorder(self) -> ApplicationRecorder:
|
|
844
|
-
"""
|
|
845
|
-
Constructs an application recorder.
|
|
846
|
-
"""
|
|
756
|
+
"""Constructs an application recorder."""
|
|
847
757
|
|
|
848
758
|
@abstractmethod
|
|
849
759
|
def tracking_recorder(
|
|
850
760
|
self, tracking_recorder_class: type[TTrackingRecorder] | None = None
|
|
851
761
|
) -> TTrackingRecorder:
|
|
852
|
-
"""
|
|
853
|
-
Constructs a tracking recorder.
|
|
854
|
-
"""
|
|
762
|
+
"""Constructs a tracking recorder."""
|
|
855
763
|
|
|
856
764
|
@abstractmethod
|
|
857
765
|
def process_recorder(self) -> ProcessRecorder:
|
|
858
|
-
"""
|
|
859
|
-
Constructs a process recorder.
|
|
860
|
-
"""
|
|
766
|
+
"""Constructs a process recorder."""
|
|
861
767
|
|
|
862
768
|
def is_snapshotting_enabled(self) -> bool:
|
|
863
|
-
"""
|
|
864
|
-
Decides whether or not snapshotting is enabled by
|
|
769
|
+
"""Decides whether or not snapshotting is enabled by
|
|
865
770
|
reading environment variable 'IS_SNAPSHOTTING_ENABLED'.
|
|
866
771
|
Snapshotting is not enabled by default.
|
|
867
772
|
"""
|
|
868
773
|
return strtobool(self.env.get(self.IS_SNAPSHOTTING_ENABLED, "no"))
|
|
869
774
|
|
|
870
775
|
def close(self) -> None:
|
|
871
|
-
"""
|
|
872
|
-
Closes any database connections, and anything else that needs closing.
|
|
873
|
-
"""
|
|
776
|
+
"""Closes any database connections, and anything else that needs closing."""
|
|
874
777
|
|
|
875
778
|
|
|
876
779
|
@dataclass(frozen=True)
|
|
877
780
|
class Tracking:
|
|
878
|
-
"""
|
|
879
|
-
Frozen dataclass representing the position of a domain
|
|
781
|
+
"""Frozen dataclass representing the position of a domain
|
|
880
782
|
event :class:`Notification` in an application's notification log.
|
|
881
783
|
"""
|
|
882
784
|
|
|
@@ -963,20 +865,15 @@ TConnection = TypeVar("TConnection", bound=Connection[Any])
|
|
|
963
865
|
|
|
964
866
|
|
|
965
867
|
class ConnectionPoolClosedError(EventSourcingError):
|
|
966
|
-
"""
|
|
967
|
-
Raised when using a connection pool that is already closed.
|
|
968
|
-
"""
|
|
868
|
+
"""Raised when using a connection pool that is already closed."""
|
|
969
869
|
|
|
970
870
|
|
|
971
871
|
class ConnectionNotFromPoolError(EventSourcingError):
|
|
972
|
-
"""
|
|
973
|
-
Raised when putting a connection in the wrong pool.
|
|
974
|
-
"""
|
|
872
|
+
"""Raised when putting a connection in the wrong pool."""
|
|
975
873
|
|
|
976
874
|
|
|
977
875
|
class ConnectionUnavailableError(OperationalError, TimeoutError):
|
|
978
|
-
"""
|
|
979
|
-
Raised when a request to get a connection from a
|
|
876
|
+
"""Raised when a request to get a connection from a
|
|
980
877
|
connection pool times out.
|
|
981
878
|
"""
|
|
982
879
|
|
|
@@ -992,8 +889,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
992
889
|
pre_ping: bool = False,
|
|
993
890
|
mutually_exclusive_read_write: bool = False,
|
|
994
891
|
) -> None:
|
|
995
|
-
"""
|
|
996
|
-
Initialises a new connection pool.
|
|
892
|
+
"""Initialises a new connection pool.
|
|
997
893
|
|
|
998
894
|
The 'pool_size' argument specifies the maximum number of connections
|
|
999
895
|
that will be put into the pool when connections are returned. The
|
|
@@ -1044,9 +940,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1044
940
|
|
|
1045
941
|
@property
|
|
1046
942
|
def num_in_use(self) -> int:
|
|
1047
|
-
"""
|
|
1048
|
-
Indicates the total number of connections currently in use.
|
|
1049
|
-
"""
|
|
943
|
+
"""Indicates the total number of connections currently in use."""
|
|
1050
944
|
with self._put_condition:
|
|
1051
945
|
return self._num_in_use
|
|
1052
946
|
|
|
@@ -1056,9 +950,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1056
950
|
|
|
1057
951
|
@property
|
|
1058
952
|
def num_in_pool(self) -> int:
|
|
1059
|
-
"""
|
|
1060
|
-
Indicates the number of connections currently in the pool.
|
|
1061
|
-
"""
|
|
953
|
+
"""Indicates the number of connections currently in the pool."""
|
|
1062
954
|
with self._put_condition:
|
|
1063
955
|
return self._num_in_pool
|
|
1064
956
|
|
|
@@ -1077,8 +969,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1077
969
|
def get_connection(
|
|
1078
970
|
self, timeout: float | None = None, is_writer: bool | None = None
|
|
1079
971
|
) -> TConnection:
|
|
1080
|
-
"""
|
|
1081
|
-
Issues connections, or raises ConnectionPoolExhausted error.
|
|
972
|
+
"""Issues connections, or raises ConnectionPoolExhausted error.
|
|
1082
973
|
Provides "fairness" on attempts to get connections, meaning that
|
|
1083
974
|
connections are issued in the same order as they are requested.
|
|
1084
975
|
|
|
@@ -1162,8 +1053,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1162
1053
|
raise ConnectionUnavailableError(msg)
|
|
1163
1054
|
|
|
1164
1055
|
def _get_connection(self, timeout: float = 0.0) -> TConnection:
|
|
1165
|
-
"""
|
|
1166
|
-
Gets or creates connections from pool within given
|
|
1056
|
+
"""Gets or creates connections from pool within given
|
|
1167
1057
|
time, otherwise raises a "pool exhausted" error.
|
|
1168
1058
|
|
|
1169
1059
|
Waits for connections to be returned if the pool
|
|
@@ -1230,8 +1120,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1230
1120
|
return conn
|
|
1231
1121
|
|
|
1232
1122
|
def put_connection(self, conn: TConnection) -> None:
|
|
1233
|
-
"""
|
|
1234
|
-
Returns connections to the pool, or closes connection
|
|
1123
|
+
"""Returns connections to the pool, or closes connection
|
|
1235
1124
|
if the pool is full.
|
|
1236
1125
|
|
|
1237
1126
|
Unlocks write lock after writer has returned, and
|
|
@@ -1240,7 +1129,6 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1240
1129
|
Notifies waiters when connections have been returned,
|
|
1241
1130
|
and when there are no longer any readers.
|
|
1242
1131
|
"""
|
|
1243
|
-
|
|
1244
1132
|
# Start forgetting if this connection was for reading or writing.
|
|
1245
1133
|
is_writer, conn.is_writer = conn.is_writer, None
|
|
1246
1134
|
|
|
@@ -1287,8 +1175,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1287
1175
|
|
|
1288
1176
|
@abstractmethod
|
|
1289
1177
|
def _create_connection(self) -> TConnection:
|
|
1290
|
-
"""
|
|
1291
|
-
Create a new connection.
|
|
1178
|
+
"""Create a new connection.
|
|
1292
1179
|
|
|
1293
1180
|
Subclasses should implement this method by
|
|
1294
1181
|
creating a database connection of the type
|
|
@@ -1296,9 +1183,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
|
|
|
1296
1183
|
"""
|
|
1297
1184
|
|
|
1298
1185
|
def close(self) -> None:
|
|
1299
|
-
"""
|
|
1300
|
-
Close the connection pool.
|
|
1301
|
-
"""
|
|
1186
|
+
"""Close the connection pool."""
|
|
1302
1187
|
with self._put_condition:
|
|
1303
1188
|
if self._closed:
|
|
1304
1189
|
return
|
|
@@ -1353,9 +1238,7 @@ class Subscription(Iterator[Notification], Generic[TApplicationRecorder_co]):
|
|
|
1353
1238
|
self.stop()
|
|
1354
1239
|
|
|
1355
1240
|
def stop(self) -> None:
|
|
1356
|
-
"""
|
|
1357
|
-
Stops the subscription.
|
|
1358
|
-
"""
|
|
1241
|
+
"""Stops the subscription."""
|
|
1359
1242
|
self._has_been_stopped = True
|
|
1360
1243
|
|
|
1361
1244
|
def __iter__(self) -> Self:
|
|
@@ -1363,9 +1246,7 @@ class Subscription(Iterator[Notification], Generic[TApplicationRecorder_co]):
|
|
|
1363
1246
|
|
|
1364
1247
|
@abstractmethod
|
|
1365
1248
|
def __next__(self) -> Notification:
|
|
1366
|
-
"""
|
|
1367
|
-
Returns the next Notification object in the application sequence.
|
|
1368
|
-
"""
|
|
1249
|
+
"""Returns the next Notification object in the application sequence."""
|
|
1369
1250
|
|
|
1370
1251
|
|
|
1371
1252
|
class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
|
|
@@ -1390,9 +1271,7 @@ class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
|
|
|
1390
1271
|
self._pull_thread.join()
|
|
1391
1272
|
|
|
1392
1273
|
def stop(self) -> None:
|
|
1393
|
-
"""
|
|
1394
|
-
Stops the subscription.
|
|
1395
|
-
"""
|
|
1274
|
+
"""Stops the subscription."""
|
|
1396
1275
|
super().stop()
|
|
1397
1276
|
self._notifications_queue.put([])
|
|
1398
1277
|
self._has_been_notified.set()
|