eventsourcing 9.2.21__py3-none-any.whl → 9.3.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.

Files changed (145) hide show
  1. eventsourcing/__init__.py +1 -1
  2. eventsourcing/application.py +137 -132
  3. eventsourcing/cipher.py +17 -12
  4. eventsourcing/compressor.py +2 -0
  5. eventsourcing/dispatch.py +30 -56
  6. eventsourcing/domain.py +221 -227
  7. eventsourcing/examples/__init__.py +0 -0
  8. eventsourcing/examples/aggregate1/__init__.py +0 -0
  9. eventsourcing/examples/aggregate1/application.py +27 -0
  10. eventsourcing/examples/aggregate1/domainmodel.py +16 -0
  11. eventsourcing/examples/aggregate1/test_application.py +37 -0
  12. eventsourcing/examples/aggregate2/__init__.py +0 -0
  13. eventsourcing/examples/aggregate2/application.py +27 -0
  14. eventsourcing/examples/aggregate2/domainmodel.py +22 -0
  15. eventsourcing/examples/aggregate2/test_application.py +37 -0
  16. eventsourcing/examples/aggregate3/__init__.py +0 -0
  17. eventsourcing/examples/aggregate3/application.py +27 -0
  18. eventsourcing/examples/aggregate3/domainmodel.py +38 -0
  19. eventsourcing/examples/aggregate3/test_application.py +37 -0
  20. eventsourcing/examples/aggregate4/__init__.py +0 -0
  21. eventsourcing/examples/aggregate4/application.py +27 -0
  22. eventsourcing/examples/aggregate4/domainmodel.py +114 -0
  23. eventsourcing/examples/aggregate4/test_application.py +38 -0
  24. eventsourcing/examples/aggregate5/__init__.py +0 -0
  25. eventsourcing/examples/aggregate5/application.py +27 -0
  26. eventsourcing/examples/aggregate5/domainmodel.py +131 -0
  27. eventsourcing/examples/aggregate5/test_application.py +38 -0
  28. eventsourcing/examples/aggregate6/__init__.py +0 -0
  29. eventsourcing/examples/aggregate6/application.py +30 -0
  30. eventsourcing/examples/aggregate6/domainmodel.py +123 -0
  31. eventsourcing/examples/aggregate6/test_application.py +38 -0
  32. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  33. eventsourcing/examples/aggregate6a/application.py +40 -0
  34. eventsourcing/examples/aggregate6a/domainmodel.py +149 -0
  35. eventsourcing/examples/aggregate6a/test_application.py +45 -0
  36. eventsourcing/examples/aggregate7/__init__.py +0 -0
  37. eventsourcing/examples/aggregate7/application.py +48 -0
  38. eventsourcing/examples/aggregate7/domainmodel.py +144 -0
  39. eventsourcing/examples/aggregate7/persistence.py +57 -0
  40. eventsourcing/examples/aggregate7/test_application.py +38 -0
  41. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +45 -0
  42. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +67 -0
  43. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  44. eventsourcing/examples/aggregate7a/application.py +56 -0
  45. eventsourcing/examples/aggregate7a/domainmodel.py +170 -0
  46. eventsourcing/examples/aggregate7a/test_application.py +46 -0
  47. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +45 -0
  48. eventsourcing/examples/aggregate8/__init__.py +0 -0
  49. eventsourcing/examples/aggregate8/application.py +47 -0
  50. eventsourcing/examples/aggregate8/domainmodel.py +65 -0
  51. eventsourcing/examples/aggregate8/persistence.py +57 -0
  52. eventsourcing/examples/aggregate8/test_application.py +37 -0
  53. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +44 -0
  54. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +38 -0
  55. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  56. eventsourcing/examples/bankaccounts/application.py +70 -0
  57. eventsourcing/examples/bankaccounts/domainmodel.py +56 -0
  58. eventsourcing/examples/bankaccounts/test.py +173 -0
  59. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  60. eventsourcing/examples/cargoshipping/application.py +126 -0
  61. eventsourcing/examples/cargoshipping/domainmodel.py +330 -0
  62. eventsourcing/examples/cargoshipping/interface.py +143 -0
  63. eventsourcing/examples/cargoshipping/test.py +231 -0
  64. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  65. eventsourcing/examples/contentmanagement/application.py +118 -0
  66. eventsourcing/examples/contentmanagement/domainmodel.py +69 -0
  67. eventsourcing/examples/contentmanagement/test.py +180 -0
  68. eventsourcing/examples/contentmanagement/utils.py +26 -0
  69. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  70. eventsourcing/examples/contentmanagementsystem/application.py +54 -0
  71. eventsourcing/examples/contentmanagementsystem/postgres.py +17 -0
  72. eventsourcing/examples/contentmanagementsystem/sqlite.py +17 -0
  73. eventsourcing/examples/contentmanagementsystem/system.py +14 -0
  74. eventsourcing/examples/contentmanagementsystem/test_system.py +180 -0
  75. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  76. eventsourcing/examples/searchablecontent/application.py +45 -0
  77. eventsourcing/examples/searchablecontent/persistence.py +23 -0
  78. eventsourcing/examples/searchablecontent/postgres.py +118 -0
  79. eventsourcing/examples/searchablecontent/sqlite.py +136 -0
  80. eventsourcing/examples/searchablecontent/test_application.py +110 -0
  81. eventsourcing/examples/searchablecontent/test_recorder.py +68 -0
  82. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  83. eventsourcing/examples/searchabletimestamps/application.py +32 -0
  84. eventsourcing/examples/searchabletimestamps/persistence.py +20 -0
  85. eventsourcing/examples/searchabletimestamps/postgres.py +110 -0
  86. eventsourcing/examples/searchabletimestamps/sqlite.py +99 -0
  87. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +94 -0
  88. eventsourcing/examples/test_invoice.py +176 -0
  89. eventsourcing/examples/test_parking_lot.py +206 -0
  90. eventsourcing/interface.py +4 -2
  91. eventsourcing/persistence.py +88 -82
  92. eventsourcing/popo.py +32 -31
  93. eventsourcing/postgres.py +388 -593
  94. eventsourcing/sqlite.py +100 -102
  95. eventsourcing/system.py +66 -71
  96. eventsourcing/tests/application.py +20 -32
  97. eventsourcing/tests/application_tests/__init__.py +0 -0
  98. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +55 -0
  99. eventsourcing/tests/application_tests/test_application_with_popo.py +22 -0
  100. eventsourcing/tests/application_tests/test_application_with_postgres.py +75 -0
  101. eventsourcing/tests/application_tests/test_application_with_sqlite.py +72 -0
  102. eventsourcing/tests/application_tests/test_cache.py +134 -0
  103. eventsourcing/tests/application_tests/test_event_sourced_log.py +162 -0
  104. eventsourcing/tests/application_tests/test_notificationlog.py +232 -0
  105. eventsourcing/tests/application_tests/test_notificationlogreader.py +126 -0
  106. eventsourcing/tests/application_tests/test_processapplication.py +110 -0
  107. eventsourcing/tests/application_tests/test_processingpolicy.py +109 -0
  108. eventsourcing/tests/application_tests/test_repository.py +504 -0
  109. eventsourcing/tests/application_tests/test_snapshotting.py +68 -0
  110. eventsourcing/tests/application_tests/test_upcasting.py +459 -0
  111. eventsourcing/tests/docs_tests/__init__.py +0 -0
  112. eventsourcing/tests/docs_tests/test_docs.py +293 -0
  113. eventsourcing/tests/domain.py +1 -1
  114. eventsourcing/tests/domain_tests/__init__.py +0 -0
  115. eventsourcing/tests/domain_tests/test_aggregate.py +1180 -0
  116. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +1604 -0
  117. eventsourcing/tests/domain_tests/test_domainevent.py +80 -0
  118. eventsourcing/tests/interface_tests/__init__.py +0 -0
  119. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +258 -0
  120. eventsourcing/tests/persistence.py +52 -50
  121. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  122. eventsourcing/tests/persistence_tests/test_aes.py +93 -0
  123. eventsourcing/tests/persistence_tests/test_connection_pool.py +722 -0
  124. eventsourcing/tests/persistence_tests/test_eventstore.py +72 -0
  125. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +21 -0
  126. eventsourcing/tests/persistence_tests/test_mapper.py +113 -0
  127. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +69 -0
  128. eventsourcing/tests/persistence_tests/test_popo.py +124 -0
  129. eventsourcing/tests/persistence_tests/test_postgres.py +1119 -0
  130. eventsourcing/tests/persistence_tests/test_sqlite.py +348 -0
  131. eventsourcing/tests/persistence_tests/test_transcoder.py +44 -0
  132. eventsourcing/tests/postgres_utils.py +7 -7
  133. eventsourcing/tests/system_tests/__init__.py +0 -0
  134. eventsourcing/tests/system_tests/test_runner.py +935 -0
  135. eventsourcing/tests/system_tests/test_system.py +284 -0
  136. eventsourcing/tests/utils_tests/__init__.py +0 -0
  137. eventsourcing/tests/utils_tests/test_utils.py +226 -0
  138. eventsourcing/utils.py +49 -50
  139. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/METADATA +30 -33
  140. eventsourcing-9.3.0.dist-info/RECORD +145 -0
  141. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/WHEEL +1 -2
  142. eventsourcing-9.2.21.dist-info/RECORD +0 -25
  143. eventsourcing-9.2.21.dist-info/top_level.txt +0 -1
  144. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/AUTHORS +0 -0
  145. {eventsourcing-9.2.21.dist-info → eventsourcing-9.3.0.dist-info}/LICENSE +0 -0
eventsourcing/sqlite.py CHANGED
@@ -1,16 +1,16 @@
1
+ from __future__ import annotations
2
+
1
3
  import sqlite3
2
4
  from contextlib import contextmanager
3
- from sqlite3 import Connection, Cursor
4
- from types import TracebackType
5
- from typing import Any, Iterator, List, Optional, Sequence, Type, Union
5
+ from typing import TYPE_CHECKING, Any, Iterator, List, Sequence, Type
6
6
  from uuid import UUID
7
7
 
8
8
  from eventsourcing.persistence import (
9
9
  AggregateRecorder,
10
10
  ApplicationRecorder,
11
- Connection as BaseConnection,
11
+ Connection,
12
12
  ConnectionPool,
13
- Cursor as BaseCursor,
13
+ Cursor,
14
14
  DatabaseError,
15
15
  DataError,
16
16
  InfrastructureFactory,
@@ -28,17 +28,20 @@ from eventsourcing.persistence import (
28
28
  )
29
29
  from eventsourcing.utils import Environment, strtobool
30
30
 
31
+ if TYPE_CHECKING: # pragma: nocover
32
+ from types import TracebackType
33
+
31
34
  SQLITE3_DEFAULT_LOCK_TIMEOUT = 5
32
35
 
33
36
 
34
- class SQLiteCursor(BaseCursor):
35
- def __init__(self, sqlite_cursor: Cursor):
37
+ class SQLiteCursor(Cursor):
38
+ def __init__(self, sqlite_cursor: sqlite3.Cursor):
36
39
  self.sqlite_cursor = sqlite_cursor
37
40
 
38
- def __enter__(self, *args: Any, **kwargs: Any) -> Cursor:
41
+ def __enter__(self) -> sqlite3.Cursor:
39
42
  return self.sqlite_cursor
40
43
 
41
- def __exit__(self, *args: Any, **kwargs: Any) -> None:
44
+ def __exit__(self, *args: object, **kwargs: Any) -> None:
42
45
  self.sqlite_cursor.close()
43
46
 
44
47
  def execute(self, *args: Any, **kwargs: Any) -> None:
@@ -58,18 +61,16 @@ class SQLiteCursor(BaseCursor):
58
61
  return self.sqlite_cursor.lastrowid
59
62
 
60
63
 
61
- class SQLiteConnection(BaseConnection[SQLiteCursor]):
62
- def __init__(self, sqlite_conn: Connection, max_age: Optional[float]):
64
+ class SQLiteConnection(Connection[SQLiteCursor]):
65
+ def __init__(self, sqlite_conn: sqlite3.Connection, max_age: float | None):
63
66
  super().__init__(max_age=max_age)
64
67
  self._sqlite_conn = sqlite_conn
65
68
 
66
69
  @contextmanager
67
- def transaction(self, commit: bool) -> Iterator[SQLiteCursor]:
68
- # Context managed transaction.
69
- with SQLiteTransaction(self, commit) as curs:
70
- # Context managed cursor.
71
- with curs:
72
- yield curs
70
+ def transaction(self, *, commit: bool) -> Iterator[SQLiteCursor]:
71
+ # Context managed cursor, and context managed transaction.
72
+ with SQLiteTransaction(self, commit=commit) as curs, curs:
73
+ yield curs
73
74
 
74
75
  def cursor(self) -> SQLiteCursor:
75
76
  return SQLiteCursor(self._sqlite_conn.cursor())
@@ -86,7 +87,7 @@ class SQLiteConnection(BaseConnection[SQLiteCursor]):
86
87
 
87
88
 
88
89
  class SQLiteTransaction:
89
- def __init__(self, connection: SQLiteConnection, commit: bool = False):
90
+ def __init__(self, connection: SQLiteConnection, *, commit: bool = False):
90
91
  self.connection = connection
91
92
  self.commit = commit
92
93
 
@@ -99,9 +100,9 @@ class SQLiteTransaction:
99
100
 
100
101
  def __exit__(
101
102
  self,
102
- exc_type: Type[BaseException],
103
- exc_val: BaseException,
104
- exc_tb: TracebackType,
103
+ exc_type: Type[BaseException] | None,
104
+ exc_val: BaseException | None,
105
+ exc_tb: TracebackType | None,
105
106
  ) -> None:
106
107
  try:
107
108
  if exc_val:
@@ -109,7 +110,7 @@ class SQLiteTransaction:
109
110
  # if an exception occurs.
110
111
  self.connection.rollback()
111
112
  raise exc_val
112
- elif not self.commit:
113
+ if not self.commit:
113
114
  self.connection.rollback()
114
115
  else:
115
116
  self.connection.commit()
@@ -136,12 +137,13 @@ class SQLiteTransaction:
136
137
  class SQLiteConnectionPool(ConnectionPool[SQLiteConnection]):
137
138
  def __init__(
138
139
  self,
140
+ *,
139
141
  db_name: str,
140
- lock_timeout: Optional[int] = None,
142
+ lock_timeout: int | None = None,
141
143
  pool_size: int = 5,
142
144
  max_overflow: int = 10,
143
145
  pool_timeout: float = 5.0,
144
- max_age: Optional[float] = None,
146
+ max_age: float | None = None,
145
147
  pre_ping: bool = False,
146
148
  ):
147
149
  self.db_name = db_name
@@ -174,20 +176,19 @@ class SQLiteConnectionPool(ConnectionPool[SQLiteConnection]):
174
176
  timeout=self.lock_timeout or SQLITE3_DEFAULT_LOCK_TIMEOUT,
175
177
  )
176
178
  except (sqlite3.Error, TypeError) as e:
177
- raise InterfaceError(e)
179
+ raise InterfaceError(e) from e
178
180
 
179
181
  # Use WAL (write-ahead log) mode if file-based database.
180
- if not self.is_sqlite_memory_mode:
181
- if not self.is_journal_mode_wal:
182
- cursor = c.cursor()
183
- cursor.execute("PRAGMA journal_mode;")
184
- mode = cursor.fetchone()[0]
185
- if mode.lower() == "wal":
186
- self.is_journal_mode_wal = True
187
- else:
188
- cursor.execute("PRAGMA journal_mode=WAL;")
189
- self.is_journal_mode_wal = True
190
- self.journal_mode_was_changed_to_wal = True
182
+ if not self.is_sqlite_memory_mode and not self.is_journal_mode_wal:
183
+ cursor = c.cursor()
184
+ cursor.execute("PRAGMA journal_mode;")
185
+ mode = cursor.fetchone()[0]
186
+ if mode.lower() == "wal":
187
+ self.is_journal_mode_wal = True
188
+ else:
189
+ cursor.execute("PRAGMA journal_mode=WAL;")
190
+ self.is_journal_mode_wal = True
191
+ self.journal_mode_was_changed_to_wal = True
191
192
 
192
193
  # Set the row factory.
193
194
  c.row_factory = sqlite3.Row
@@ -200,11 +201,12 @@ class SQLiteDatastore:
200
201
  def __init__(
201
202
  self,
202
203
  db_name: str,
203
- lock_timeout: Optional[int] = None,
204
+ *,
205
+ lock_timeout: int | None = None,
204
206
  pool_size: int = 5,
205
207
  max_overflow: int = 10,
206
208
  pool_timeout: float = 5.0,
207
- max_age: Optional[float] = None,
209
+ max_age: float | None = None,
208
210
  pre_ping: bool = False,
209
211
  ):
210
212
  self.pool = SQLiteConnectionPool(
@@ -218,13 +220,13 @@ class SQLiteDatastore:
218
220
  )
219
221
 
220
222
  @contextmanager
221
- def transaction(self, commit: bool) -> Iterator[SQLiteCursor]:
222
- with self.get_connection(commit=commit) as conn:
223
- with conn.transaction(commit) as curs:
224
- yield curs
223
+ def transaction(self, *, commit: bool) -> Iterator[SQLiteCursor]:
224
+ connection = self.get_connection(commit=commit)
225
+ with connection as conn, conn.transaction(commit=commit) as curs:
226
+ yield curs
225
227
 
226
228
  @contextmanager
227
- def get_connection(self, commit: bool) -> Iterator[SQLiteConnection]:
229
+ def get_connection(self, *, commit: bool) -> Iterator[SQLiteConnection]:
228
230
  # Using reader-writer interlocking is necessary for in-memory databases,
229
231
  # but also speeds up (and provides "fairness") to file-based databases.
230
232
  conn = self.pool.get_connection(is_writer=commit)
@@ -254,7 +256,7 @@ class SQLiteAggregateRecorder(AggregateRecorder):
254
256
  f"INSERT INTO {self.events_table_name} VALUES (?,?,?,?)"
255
257
  )
256
258
  self.select_events_statement = (
257
- "SELECT * " f"FROM {self.events_table_name} " "WHERE originator_id=? "
259
+ f"SELECT * FROM {self.events_table_name} WHERE originator_id=? "
258
260
  )
259
261
 
260
262
  def construct_create_table_statements(self) -> List[str]:
@@ -275,11 +277,10 @@ class SQLiteAggregateRecorder(AggregateRecorder):
275
277
  with self.datastore.transaction(commit=True) as c:
276
278
  for statement in self.create_table_statements:
277
279
  c.execute(statement)
278
- pass # for Coverage 5.5 bug with CPython 3.10.0rc1
279
280
 
280
281
  def insert_events(
281
282
  self, stored_events: List[StoredEvent], **kwargs: Any
282
- ) -> Optional[Sequence[int]]:
283
+ ) -> Sequence[int] | None:
283
284
  with self.datastore.transaction(commit=True) as c:
284
285
  return self._insert_events(c, stored_events, **kwargs)
285
286
 
@@ -287,28 +288,28 @@ class SQLiteAggregateRecorder(AggregateRecorder):
287
288
  self,
288
289
  c: SQLiteCursor,
289
290
  stored_events: List[StoredEvent],
290
- **kwargs: Any,
291
- ) -> Optional[Sequence[int]]:
292
- params = []
293
- for stored_event in stored_events:
294
- params.append(
295
- (
296
- stored_event.originator_id.hex,
297
- stored_event.originator_version,
298
- stored_event.topic,
299
- stored_event.state,
300
- )
291
+ **_: Any,
292
+ ) -> Sequence[int] | None:
293
+ params = [
294
+ (
295
+ s.originator_id.hex,
296
+ s.originator_version,
297
+ s.topic,
298
+ s.state,
301
299
  )
300
+ for s in stored_events
301
+ ]
302
302
  c.executemany(self.insert_events_statement, params)
303
303
  return None
304
304
 
305
305
  def select_events(
306
306
  self,
307
307
  originator_id: UUID,
308
- gt: Optional[int] = None,
309
- lte: Optional[int] = None,
308
+ *,
309
+ gt: int | None = None,
310
+ lte: int | None = None,
310
311
  desc: bool = False,
311
- limit: Optional[int] = None,
312
+ limit: int | None = None,
312
313
  ) -> List[StoredEvent]:
313
314
  statement = self.select_events_statement
314
315
  params: List[Any] = [originator_id.hex]
@@ -326,20 +327,17 @@ class SQLiteAggregateRecorder(AggregateRecorder):
326
327
  if limit is not None:
327
328
  statement += "LIMIT ? "
328
329
  params.append(limit)
329
- stored_events = []
330
330
  with self.datastore.transaction(commit=False) as c:
331
331
  c.execute(statement, params)
332
- for row in c.fetchall():
333
- stored_events.append(
334
- StoredEvent(
335
- originator_id=UUID(row["originator_id"]),
336
- originator_version=row["originator_version"],
337
- topic=row["topic"],
338
- state=row["state"],
339
- )
332
+ return [
333
+ StoredEvent(
334
+ originator_id=UUID(row["originator_id"]),
335
+ originator_version=row["originator_version"],
336
+ topic=row["topic"],
337
+ state=row["state"],
340
338
  )
341
- pass # for Coverage 5.5 bug with CPython 3.10.0rc1
342
- return stored_events
339
+ for row in c.fetchall()
340
+ ]
343
341
 
344
342
 
345
343
  class SQLiteApplicationRecorder(
@@ -373,8 +371,8 @@ class SQLiteApplicationRecorder(
373
371
  self,
374
372
  c: SQLiteCursor,
375
373
  stored_events: List[StoredEvent],
376
- **kwargs: Any,
377
- ) -> Optional[Sequence[int]]:
374
+ **_: Any,
375
+ ) -> Sequence[int] | None:
378
376
  returning = []
379
377
  for stored_event in stored_events:
380
378
  c.execute(
@@ -393,17 +391,15 @@ class SQLiteApplicationRecorder(
393
391
  self,
394
392
  start: int,
395
393
  limit: int,
396
- stop: Optional[int] = None,
394
+ stop: int | None = None,
397
395
  topics: Sequence[str] = (),
398
396
  ) -> List[Notification]:
399
397
  """
400
398
  Returns a list of event notifications
401
399
  from 'start', limited by 'limit'.
402
400
  """
403
- notifications = []
404
-
405
- params: List[Union[int, str]] = [start]
406
- statement = f"SELECT rowid, * FROM {self.events_table_name} " "WHERE rowid>=? "
401
+ params: List[int | str] = [start]
402
+ statement = f"SELECT rowid, * FROM {self.events_table_name} WHERE rowid>=? "
407
403
 
408
404
  if stop is not None:
409
405
  params.append(stop)
@@ -418,19 +414,16 @@ class SQLiteApplicationRecorder(
418
414
 
419
415
  with self.datastore.transaction(commit=False) as c:
420
416
  c.execute(statement, params)
421
-
422
- for row in c.fetchall():
423
- notifications.append(
424
- Notification(
425
- id=row["rowid"],
426
- originator_id=UUID(row["originator_id"]),
427
- originator_version=row["originator_version"],
428
- topic=row["topic"],
429
- state=row["state"],
430
- )
417
+ return [
418
+ Notification(
419
+ id=row["rowid"],
420
+ originator_id=UUID(row["originator_id"]),
421
+ originator_version=row["originator_version"],
422
+ topic=row["topic"],
423
+ state=row["state"],
431
424
  )
432
- pass # for Coverage 5.5 bug with CPython 3.10.0rc1
433
- return notifications
425
+ for row in c.fetchall()
426
+ ]
434
427
 
435
428
  def max_notification_id(self) -> int:
436
429
  """
@@ -479,8 +472,7 @@ class SQLiteProcessRecorder(
479
472
  params = [application_name]
480
473
  with self.datastore.transaction(commit=False) as c:
481
474
  c.execute(self.select_max_tracking_id_statement, params)
482
- max_id = c.fetchone()[0] or 0
483
- return max_id
475
+ return c.fetchone()[0] or 0
484
476
 
485
477
  def has_tracking_id(self, application_name: str, notification_id: int) -> bool:
486
478
  params = [application_name, notification_id]
@@ -493,9 +485,9 @@ class SQLiteProcessRecorder(
493
485
  c: SQLiteCursor,
494
486
  stored_events: List[StoredEvent],
495
487
  **kwargs: Any,
496
- ) -> Optional[Sequence[int]]:
488
+ ) -> Sequence[int] | None:
497
489
  returning = super()._insert_events(c, stored_events, **kwargs)
498
- tracking: Optional[Tracking] = kwargs.get("tracking", None)
490
+ tracking: Tracking | None = kwargs.get("tracking", None)
499
491
  if tracking is not None:
500
492
  c.execute(
501
493
  self.insert_tracking_statement,
@@ -512,37 +504,43 @@ class Factory(InfrastructureFactory):
512
504
  SQLITE_LOCK_TIMEOUT = "SQLITE_LOCK_TIMEOUT"
513
505
  CREATE_TABLE = "CREATE_TABLE"
514
506
 
507
+ aggregate_recorder_class = SQLiteAggregateRecorder
508
+ application_recorder_class = SQLiteApplicationRecorder
509
+ process_recorder_class = SQLiteProcessRecorder
510
+
515
511
  def __init__(self, env: Environment):
516
512
  super().__init__(env)
517
513
  db_name = self.env.get(self.SQLITE_DBNAME)
518
514
  if not db_name:
519
- raise EnvironmentError(
515
+ msg = (
520
516
  "SQLite database name not found "
521
517
  "in environment with keys: "
522
518
  f"{', '.join(self.env.create_keys(self.SQLITE_DBNAME))}"
523
519
  )
520
+ raise OSError(msg)
524
521
 
525
522
  lock_timeout_str = (
526
523
  self.env.get(self.SQLITE_LOCK_TIMEOUT) or ""
527
524
  ).strip() or None
528
525
 
529
- lock_timeout: Optional[int] = None
526
+ lock_timeout: int | None = None
530
527
  if lock_timeout_str is not None:
531
528
  try:
532
529
  lock_timeout = int(lock_timeout_str)
533
530
  except ValueError:
534
- raise EnvironmentError(
535
- f"SQLite environment value for key "
531
+ msg = (
532
+ "SQLite environment value for key "
536
533
  f"'{self.SQLITE_LOCK_TIMEOUT}' is invalid. "
537
- f"If set, an int or empty string is expected: "
534
+ "If set, an int or empty string is expected: "
538
535
  f"'{lock_timeout_str}'"
539
536
  )
537
+ raise OSError(msg) from None
540
538
 
541
539
  self.datastore = SQLiteDatastore(db_name=db_name, lock_timeout=lock_timeout)
542
540
 
543
541
  def aggregate_recorder(self, purpose: str = "events") -> AggregateRecorder:
544
542
  events_table_name = "stored_" + purpose
545
- recorder = SQLiteAggregateRecorder(
543
+ recorder = self.aggregate_recorder_class(
546
544
  datastore=self.datastore,
547
545
  events_table_name=events_table_name,
548
546
  )
@@ -551,13 +549,13 @@ class Factory(InfrastructureFactory):
551
549
  return recorder
552
550
 
553
551
  def application_recorder(self) -> ApplicationRecorder:
554
- recorder = SQLiteApplicationRecorder(datastore=self.datastore)
552
+ recorder = self.application_recorder_class(datastore=self.datastore)
555
553
  if self.env_create_table():
556
554
  recorder.create_table()
557
555
  return recorder
558
556
 
559
557
  def process_recorder(self) -> ProcessRecorder:
560
- recorder = SQLiteProcessRecorder(datastore=self.datastore)
558
+ recorder = self.process_recorder_class(datastore=self.datastore)
561
559
  if self.env_create_table():
562
560
  recorder.create_table()
563
561
  return recorder