eventsourcing 9.3.5__py3-none-any.whl → 9.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of eventsourcing might be problematic. Click here for more details.
- eventsourcing/__init__.py +0 -1
- eventsourcing/application.py +115 -173
- eventsourcing/cipher.py +9 -10
- eventsourcing/compressor.py +2 -6
- eventsourcing/cryptography.py +91 -0
- eventsourcing/dispatch.py +52 -11
- eventsourcing/domain.py +733 -690
- eventsourcing/interface.py +39 -32
- eventsourcing/persistence.py +412 -287
- eventsourcing/popo.py +136 -44
- eventsourcing/postgres.py +404 -187
- eventsourcing/projection.py +428 -0
- eventsourcing/sqlite.py +167 -55
- eventsourcing/system.py +230 -341
- eventsourcing/tests/__init__.py +3 -0
- eventsourcing/tests/application.py +195 -129
- eventsourcing/tests/domain.py +19 -37
- eventsourcing/tests/persistence.py +533 -235
- eventsourcing/tests/postgres_utils.py +12 -9
- eventsourcing/utils.py +39 -47
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/LICENSE +1 -1
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/METADATA +14 -13
- eventsourcing-9.4.0.dist-info/RECORD +26 -0
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/WHEEL +1 -1
- eventsourcing-9.3.5.dist-info/RECORD +0 -24
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/AUTHORS +0 -0
eventsourcing/sqlite.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sqlite3
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
from eventsourcing.persistence import (
|
|
@@ -23,12 +23,16 @@ from eventsourcing.persistence import (
|
|
|
23
23
|
PersistenceError,
|
|
24
24
|
ProcessRecorder,
|
|
25
25
|
ProgrammingError,
|
|
26
|
+
Recorder,
|
|
26
27
|
StoredEvent,
|
|
28
|
+
Subscription,
|
|
27
29
|
Tracking,
|
|
30
|
+
TrackingRecorder,
|
|
28
31
|
)
|
|
29
|
-
from eventsourcing.utils import Environment, strtobool
|
|
32
|
+
from eventsourcing.utils import Environment, resolve_topic, strtobool
|
|
30
33
|
|
|
31
|
-
if TYPE_CHECKING:
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from collections.abc import Iterator, Sequence
|
|
32
36
|
from types import TracebackType
|
|
33
37
|
|
|
34
38
|
SQLITE3_DEFAULT_LOCK_TIMEOUT = 5
|
|
@@ -100,7 +104,7 @@ class SQLiteTransaction:
|
|
|
100
104
|
|
|
101
105
|
def __exit__(
|
|
102
106
|
self,
|
|
103
|
-
exc_type:
|
|
107
|
+
exc_type: type[BaseException] | None,
|
|
104
108
|
exc_val: BaseException | None,
|
|
105
109
|
exc_tb: TracebackType | None,
|
|
106
110
|
) -> None:
|
|
@@ -242,16 +246,32 @@ class SQLiteDatastore:
|
|
|
242
246
|
self.close()
|
|
243
247
|
|
|
244
248
|
|
|
245
|
-
class
|
|
249
|
+
class SQLiteRecorder(Recorder):
|
|
246
250
|
def __init__(
|
|
247
251
|
self,
|
|
248
252
|
datastore: SQLiteDatastore,
|
|
249
|
-
events_table_name: str = "stored_events",
|
|
250
253
|
):
|
|
251
254
|
assert isinstance(datastore, SQLiteDatastore)
|
|
252
255
|
self.datastore = datastore
|
|
253
|
-
self.events_table_name = events_table_name
|
|
254
256
|
self.create_table_statements = self.construct_create_table_statements()
|
|
257
|
+
|
|
258
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
259
|
+
return []
|
|
260
|
+
|
|
261
|
+
def create_table(self) -> None:
|
|
262
|
+
with self.datastore.transaction(commit=True) as c:
|
|
263
|
+
for statement in self.create_table_statements:
|
|
264
|
+
c.execute(statement)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
268
|
+
def __init__(
|
|
269
|
+
self,
|
|
270
|
+
datastore: SQLiteDatastore,
|
|
271
|
+
events_table_name: str = "stored_events",
|
|
272
|
+
):
|
|
273
|
+
self.events_table_name = events_table_name
|
|
274
|
+
super().__init__(datastore)
|
|
255
275
|
self.insert_events_statement = (
|
|
256
276
|
f"INSERT INTO {self.events_table_name} VALUES (?,?,?,?)"
|
|
257
277
|
)
|
|
@@ -259,8 +279,9 @@ class SQLiteAggregateRecorder(AggregateRecorder):
|
|
|
259
279
|
f"SELECT * FROM {self.events_table_name} WHERE originator_id=? "
|
|
260
280
|
)
|
|
261
281
|
|
|
262
|
-
def construct_create_table_statements(self) ->
|
|
263
|
-
|
|
282
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
283
|
+
statements = super().construct_create_table_statements()
|
|
284
|
+
statements.append(
|
|
264
285
|
"CREATE TABLE IF NOT EXISTS "
|
|
265
286
|
f"{self.events_table_name} ("
|
|
266
287
|
"originator_id TEXT, "
|
|
@@ -271,15 +292,10 @@ class SQLiteAggregateRecorder(AggregateRecorder):
|
|
|
271
292
|
"(originator_id, originator_version)) "
|
|
272
293
|
"WITHOUT ROWID"
|
|
273
294
|
)
|
|
274
|
-
return
|
|
275
|
-
|
|
276
|
-
def create_table(self) -> None:
|
|
277
|
-
with self.datastore.transaction(commit=True) as c:
|
|
278
|
-
for statement in self.create_table_statements:
|
|
279
|
-
c.execute(statement)
|
|
295
|
+
return statements
|
|
280
296
|
|
|
281
297
|
def insert_events(
|
|
282
|
-
self, stored_events:
|
|
298
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
283
299
|
) -> Sequence[int] | None:
|
|
284
300
|
with self.datastore.transaction(commit=True) as c:
|
|
285
301
|
return self._insert_events(c, stored_events, **kwargs)
|
|
@@ -287,7 +303,7 @@ class SQLiteAggregateRecorder(AggregateRecorder):
|
|
|
287
303
|
def _insert_events(
|
|
288
304
|
self,
|
|
289
305
|
c: SQLiteCursor,
|
|
290
|
-
stored_events:
|
|
306
|
+
stored_events: list[StoredEvent],
|
|
291
307
|
**_: Any,
|
|
292
308
|
) -> Sequence[int] | None:
|
|
293
309
|
params = [
|
|
@@ -310,9 +326,9 @@ class SQLiteAggregateRecorder(AggregateRecorder):
|
|
|
310
326
|
lte: int | None = None,
|
|
311
327
|
desc: bool = False,
|
|
312
328
|
limit: int | None = None,
|
|
313
|
-
) ->
|
|
329
|
+
) -> list[StoredEvent]:
|
|
314
330
|
statement = self.select_events_statement
|
|
315
|
-
params:
|
|
331
|
+
params: list[Any] = [originator_id.hex]
|
|
316
332
|
if gt is not None:
|
|
317
333
|
statement += "AND originator_version>? "
|
|
318
334
|
params.append(gt)
|
|
@@ -354,7 +370,7 @@ class SQLiteApplicationRecorder(
|
|
|
354
370
|
f"SELECT MAX(rowid) FROM {self.events_table_name}"
|
|
355
371
|
)
|
|
356
372
|
|
|
357
|
-
def construct_create_table_statements(self) ->
|
|
373
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
358
374
|
statement = (
|
|
359
375
|
"CREATE TABLE IF NOT EXISTS "
|
|
360
376
|
f"{self.events_table_name} ("
|
|
@@ -370,7 +386,7 @@ class SQLiteApplicationRecorder(
|
|
|
370
386
|
def _insert_events(
|
|
371
387
|
self,
|
|
372
388
|
c: SQLiteCursor,
|
|
373
|
-
stored_events:
|
|
389
|
+
stored_events: list[StoredEvent],
|
|
374
390
|
**_: Any,
|
|
375
391
|
) -> Sequence[int] | None:
|
|
376
392
|
returning = []
|
|
@@ -389,25 +405,44 @@ class SQLiteApplicationRecorder(
|
|
|
389
405
|
|
|
390
406
|
def select_notifications(
|
|
391
407
|
self,
|
|
392
|
-
start: int,
|
|
408
|
+
start: int | None,
|
|
393
409
|
limit: int,
|
|
394
410
|
stop: int | None = None,
|
|
395
411
|
topics: Sequence[str] = (),
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
412
|
+
*,
|
|
413
|
+
inclusive_of_start: bool = True,
|
|
414
|
+
) -> list[Notification]:
|
|
415
|
+
"""Returns a list of event notifications
|
|
399
416
|
from 'start', limited by 'limit'.
|
|
400
417
|
"""
|
|
401
|
-
params:
|
|
402
|
-
statement = f"SELECT rowid, * FROM {self.events_table_name}
|
|
418
|
+
params: list[int | str] = []
|
|
419
|
+
statement = f"SELECT rowid, * FROM {self.events_table_name} "
|
|
420
|
+
has_where = False
|
|
421
|
+
if start is not None:
|
|
422
|
+
has_where = True
|
|
423
|
+
statement += "WHERE "
|
|
424
|
+
params.append(start)
|
|
425
|
+
if inclusive_of_start:
|
|
426
|
+
statement += "rowid>=? "
|
|
427
|
+
else:
|
|
428
|
+
statement += "rowid>? "
|
|
403
429
|
|
|
404
430
|
if stop is not None:
|
|
431
|
+
if not has_where:
|
|
432
|
+
has_where = True
|
|
433
|
+
statement += "WHERE "
|
|
434
|
+
else:
|
|
435
|
+
statement += "AND "
|
|
405
436
|
params.append(stop)
|
|
406
|
-
statement += "
|
|
437
|
+
statement += "rowid<=? "
|
|
407
438
|
|
|
408
439
|
if topics:
|
|
440
|
+
if not has_where:
|
|
441
|
+
statement += "WHERE "
|
|
442
|
+
else:
|
|
443
|
+
statement += "AND "
|
|
409
444
|
params += list(topics)
|
|
410
|
-
statement += "
|
|
445
|
+
statement += f"topic IN ({','.join('?' * len(topics))}) "
|
|
411
446
|
|
|
412
447
|
params.append(limit)
|
|
413
448
|
statement += "ORDER BY rowid LIMIT ?"
|
|
@@ -426,27 +461,29 @@ class SQLiteApplicationRecorder(
|
|
|
426
461
|
]
|
|
427
462
|
|
|
428
463
|
def max_notification_id(self) -> int:
|
|
429
|
-
"""
|
|
430
|
-
Returns the maximum notification ID.
|
|
431
|
-
"""
|
|
464
|
+
"""Returns the maximum notification ID."""
|
|
432
465
|
with self.datastore.transaction(commit=False) as c:
|
|
433
466
|
return self._max_notification_id(c)
|
|
434
467
|
|
|
435
468
|
def _max_notification_id(self, c: SQLiteCursor) -> int:
|
|
436
469
|
c.execute(self.select_max_notification_id_statement)
|
|
437
|
-
return c.fetchone()[0]
|
|
470
|
+
return c.fetchone()[0]
|
|
438
471
|
|
|
472
|
+
def subscribe(
|
|
473
|
+
self, gt: int | None = None, topics: Sequence[str] = ()
|
|
474
|
+
) -> Subscription[ApplicationRecorder]:
|
|
475
|
+
"""This method is not implemented on this class."""
|
|
476
|
+
msg = f"The {type(self).__qualname__} recorder does not support subscriptions"
|
|
477
|
+
raise NotImplementedError(msg)
|
|
439
478
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
ProcessRecorder,
|
|
443
|
-
):
|
|
479
|
+
|
|
480
|
+
class SQLiteTrackingRecorder(SQLiteRecorder, TrackingRecorder):
|
|
444
481
|
def __init__(
|
|
445
482
|
self,
|
|
446
483
|
datastore: SQLiteDatastore,
|
|
447
|
-
|
|
484
|
+
**kwargs: Any,
|
|
448
485
|
):
|
|
449
|
-
super().__init__(datastore,
|
|
486
|
+
super().__init__(datastore, **kwargs)
|
|
450
487
|
self.insert_tracking_statement = "INSERT INTO tracking VALUES (?,?)"
|
|
451
488
|
self.select_max_tracking_id_statement = (
|
|
452
489
|
"SELECT MAX(notification_id) FROM tracking WHERE application_name=?"
|
|
@@ -456,7 +493,7 @@ class SQLiteProcessRecorder(
|
|
|
456
493
|
"application_name=? AND notification_id=?"
|
|
457
494
|
)
|
|
458
495
|
|
|
459
|
-
def construct_create_table_statements(self) ->
|
|
496
|
+
def construct_create_table_statements(self) -> list[str]:
|
|
460
497
|
statements = super().construct_create_table_statements()
|
|
461
498
|
statements.append(
|
|
462
499
|
"CREATE TABLE IF NOT EXISTS tracking ("
|
|
@@ -468,44 +505,74 @@ class SQLiteProcessRecorder(
|
|
|
468
505
|
)
|
|
469
506
|
return statements
|
|
470
507
|
|
|
471
|
-
def
|
|
508
|
+
def insert_tracking(self, tracking: Tracking) -> None:
|
|
509
|
+
with self.datastore.transaction(commit=True) as c:
|
|
510
|
+
self._insert_tracking(c, tracking)
|
|
511
|
+
|
|
512
|
+
def _insert_tracking(
|
|
513
|
+
self,
|
|
514
|
+
c: SQLiteCursor,
|
|
515
|
+
tracking: Tracking,
|
|
516
|
+
) -> None:
|
|
517
|
+
c.execute(
|
|
518
|
+
self.insert_tracking_statement,
|
|
519
|
+
(
|
|
520
|
+
tracking.application_name,
|
|
521
|
+
tracking.notification_id,
|
|
522
|
+
),
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
def max_tracking_id(self, application_name: str) -> int | None:
|
|
472
526
|
params = [application_name]
|
|
473
527
|
with self.datastore.transaction(commit=False) as c:
|
|
474
528
|
c.execute(self.select_max_tracking_id_statement, params)
|
|
475
|
-
return c.fetchone()[0]
|
|
529
|
+
return c.fetchone()[0]
|
|
476
530
|
|
|
477
|
-
def has_tracking_id(
|
|
531
|
+
def has_tracking_id(
|
|
532
|
+
self, application_name: str, notification_id: int | None
|
|
533
|
+
) -> bool:
|
|
534
|
+
if notification_id is None:
|
|
535
|
+
return True
|
|
478
536
|
params = [application_name, notification_id]
|
|
479
537
|
with self.datastore.transaction(commit=False) as c:
|
|
480
538
|
c.execute(self.count_tracking_id_statement, params)
|
|
481
539
|
return bool(c.fetchone()[0])
|
|
482
540
|
|
|
541
|
+
|
|
542
|
+
class SQLiteProcessRecorder(
|
|
543
|
+
SQLiteTrackingRecorder,
|
|
544
|
+
SQLiteApplicationRecorder,
|
|
545
|
+
ProcessRecorder,
|
|
546
|
+
):
|
|
547
|
+
def __init__(
|
|
548
|
+
self,
|
|
549
|
+
datastore: SQLiteDatastore,
|
|
550
|
+
*,
|
|
551
|
+
events_table_name: str = "stored_events",
|
|
552
|
+
):
|
|
553
|
+
super().__init__(datastore, events_table_name=events_table_name)
|
|
554
|
+
|
|
483
555
|
def _insert_events(
|
|
484
556
|
self,
|
|
485
557
|
c: SQLiteCursor,
|
|
486
|
-
stored_events:
|
|
558
|
+
stored_events: list[StoredEvent],
|
|
487
559
|
**kwargs: Any,
|
|
488
560
|
) -> Sequence[int] | None:
|
|
489
561
|
returning = super()._insert_events(c, stored_events, **kwargs)
|
|
490
|
-
tracking: Tracking | None = kwargs.get("tracking"
|
|
562
|
+
tracking: Tracking | None = kwargs.get("tracking")
|
|
491
563
|
if tracking is not None:
|
|
492
|
-
|
|
493
|
-
self.insert_tracking_statement,
|
|
494
|
-
(
|
|
495
|
-
tracking.application_name,
|
|
496
|
-
tracking.notification_id,
|
|
497
|
-
),
|
|
498
|
-
)
|
|
564
|
+
self._insert_tracking(c, tracking)
|
|
499
565
|
return returning
|
|
500
566
|
|
|
501
567
|
|
|
502
|
-
class
|
|
568
|
+
class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
503
569
|
SQLITE_DBNAME = "SQLITE_DBNAME"
|
|
504
570
|
SQLITE_LOCK_TIMEOUT = "SQLITE_LOCK_TIMEOUT"
|
|
505
571
|
CREATE_TABLE = "CREATE_TABLE"
|
|
506
572
|
|
|
507
573
|
aggregate_recorder_class = SQLiteAggregateRecorder
|
|
508
574
|
application_recorder_class = SQLiteApplicationRecorder
|
|
575
|
+
tracking_recorder_class = SQLiteTrackingRecorder
|
|
509
576
|
process_recorder_class = SQLiteProcessRecorder
|
|
510
577
|
|
|
511
578
|
def __init__(self, env: Environment):
|
|
@@ -549,13 +616,55 @@ class Factory(InfrastructureFactory):
|
|
|
549
616
|
return recorder
|
|
550
617
|
|
|
551
618
|
def application_recorder(self) -> ApplicationRecorder:
|
|
552
|
-
|
|
619
|
+
application_recorder_topic = self.env.get(self.APPLICATION_RECORDER_TOPIC)
|
|
620
|
+
|
|
621
|
+
if application_recorder_topic:
|
|
622
|
+
application_recorder_class: type[SQLiteApplicationRecorder] = resolve_topic(
|
|
623
|
+
application_recorder_topic
|
|
624
|
+
)
|
|
625
|
+
assert issubclass(application_recorder_class, SQLiteApplicationRecorder)
|
|
626
|
+
else:
|
|
627
|
+
application_recorder_class = self.application_recorder_class
|
|
628
|
+
|
|
629
|
+
recorder = application_recorder_class(datastore=self.datastore)
|
|
630
|
+
|
|
631
|
+
if self.env_create_table():
|
|
632
|
+
recorder.create_table()
|
|
633
|
+
return recorder
|
|
634
|
+
|
|
635
|
+
def tracking_recorder(
|
|
636
|
+
self, tracking_recorder_class: type[SQLiteTrackingRecorder] | None = None
|
|
637
|
+
) -> SQLiteTrackingRecorder:
|
|
638
|
+
if tracking_recorder_class is None:
|
|
639
|
+
tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
|
|
640
|
+
|
|
641
|
+
if tracking_recorder_topic:
|
|
642
|
+
tracking_recorder_class = resolve_topic(tracking_recorder_topic)
|
|
643
|
+
else:
|
|
644
|
+
tracking_recorder_class = self.tracking_recorder_class
|
|
645
|
+
|
|
646
|
+
assert tracking_recorder_class is not None
|
|
647
|
+
assert issubclass(tracking_recorder_class, SQLiteTrackingRecorder)
|
|
648
|
+
|
|
649
|
+
recorder = tracking_recorder_class(datastore=self.datastore)
|
|
650
|
+
|
|
553
651
|
if self.env_create_table():
|
|
554
652
|
recorder.create_table()
|
|
555
653
|
return recorder
|
|
556
654
|
|
|
557
655
|
def process_recorder(self) -> ProcessRecorder:
|
|
558
|
-
|
|
656
|
+
process_recorder_topic = self.env.get(self.PROCESS_RECORDER_TOPIC)
|
|
657
|
+
|
|
658
|
+
if process_recorder_topic:
|
|
659
|
+
process_recorder_class: type[SQLiteProcessRecorder] = resolve_topic(
|
|
660
|
+
process_recorder_topic
|
|
661
|
+
)
|
|
662
|
+
assert issubclass(process_recorder_class, SQLiteProcessRecorder)
|
|
663
|
+
else:
|
|
664
|
+
process_recorder_class = self.process_recorder_class
|
|
665
|
+
|
|
666
|
+
recorder = process_recorder_class(datastore=self.datastore)
|
|
667
|
+
|
|
559
668
|
if self.env_create_table():
|
|
560
669
|
recorder.create_table()
|
|
561
670
|
return recorder
|
|
@@ -566,3 +675,6 @@ class Factory(InfrastructureFactory):
|
|
|
566
675
|
|
|
567
676
|
def close(self) -> None:
|
|
568
677
|
self.datastore.close()
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
Factory = SQLiteFactory
|