eventsourcing 9.3.4__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/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, Iterator, List, Sequence, Type
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: # pragma: nocover
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: Type[BaseException] | None,
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 SQLiteAggregateRecorder(AggregateRecorder):
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) -> List[str]:
263
- statement = (
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 [statement]
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: List[StoredEvent], **kwargs: Any
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: List[StoredEvent],
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
- ) -> List[StoredEvent]:
329
+ ) -> list[StoredEvent]:
314
330
  statement = self.select_events_statement
315
- params: List[Any] = [originator_id.hex]
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) -> List[str]:
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: List[StoredEvent],
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
- ) -> List[Notification]:
397
- """
398
- Returns a list of event notifications
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: List[int | str] = [start]
402
- statement = f"SELECT rowid, * FROM {self.events_table_name} WHERE rowid>=? "
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 += "AND rowid<=? "
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 += "AND topic IN (%s) " % ",".join("?" * len(topics))
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] or 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
- class SQLiteProcessRecorder(
441
- SQLiteApplicationRecorder,
442
- ProcessRecorder,
443
- ):
479
+
480
+ class SQLiteTrackingRecorder(SQLiteRecorder, TrackingRecorder):
444
481
  def __init__(
445
482
  self,
446
483
  datastore: SQLiteDatastore,
447
- events_table_name: str = "stored_events",
484
+ **kwargs: Any,
448
485
  ):
449
- super().__init__(datastore, events_table_name)
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) -> List[str]:
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 max_tracking_id(self, application_name: str) -> int:
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] or 0
529
+ return c.fetchone()[0]
476
530
 
477
- def has_tracking_id(self, application_name: str, notification_id: int) -> bool:
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: List[StoredEvent],
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", None)
562
+ tracking: Tracking | None = kwargs.get("tracking")
491
563
  if tracking is not None:
492
- c.execute(
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 Factory(InfrastructureFactory):
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
- recorder = self.application_recorder_class(datastore=self.datastore)
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
- recorder = self.process_recorder_class(datastore=self.datastore)
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