eventsourcing 9.4.0a4__py3-none-any.whl → 9.4.0a6__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.

@@ -4,27 +4,15 @@ import json
4
4
  import uuid
5
5
  from abc import ABC, abstractmethod
6
6
  from collections import deque
7
+ from collections.abc import Iterator, Mapping, Sequence
7
8
  from dataclasses import dataclass
8
9
  from datetime import datetime
9
10
  from decimal import Decimal
10
11
  from queue import Queue
11
12
  from threading import Condition, Event, Lock, Semaphore, Thread, Timer
12
13
  from time import monotonic, sleep, time
13
- from types import ModuleType
14
- from typing import (
15
- TYPE_CHECKING,
16
- Any,
17
- Dict,
18
- Generic,
19
- Iterator,
20
- List,
21
- Mapping,
22
- Sequence,
23
- Type,
24
- TypeVar,
25
- Union,
26
- cast,
27
- )
14
+ from types import GenericAlias, ModuleType
15
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast
28
16
  from uuid import UUID
29
17
  from warnings import warn
30
18
 
@@ -78,8 +66,8 @@ class JSONTranscoder(Transcoder):
78
66
  """
79
67
 
80
68
  def __init__(self) -> None:
81
- self.types: Dict[type, Transcoding] = {}
82
- self.names: Dict[str, Transcoding] = {}
69
+ self.types: dict[type, Transcoding] = {}
70
+ self.names: dict[str, Transcoding] = {}
83
71
  self.encoder = json.JSONEncoder(
84
72
  default=self._encode_obj,
85
73
  separators=(",", ":"),
@@ -106,7 +94,7 @@ class JSONTranscoder(Transcoder):
106
94
  """
107
95
  return self.decoder.decode(data.decode("utf8"))
108
96
 
109
- def _encode_obj(self, o: Any) -> Dict[str, Any]:
97
+ def _encode_obj(self, o: Any) -> dict[str, Any]:
110
98
  try:
111
99
  transcoding = self.types[type(o)]
112
100
  except KeyError:
@@ -122,7 +110,7 @@ class JSONTranscoder(Transcoder):
122
110
  "_data_": transcoding.encode(o),
123
111
  }
124
112
 
125
- def _decode_obj(self, d: Dict[str, Any]) -> Any:
113
+ def _decode_obj(self, d: dict[str, Any]) -> Any:
126
114
  if len(d) == 2:
127
115
  try:
128
116
  _type_ = d["_type_"]
@@ -314,7 +302,7 @@ class Mapper:
314
302
  stored_state = self.cipher.decrypt(stored_state)
315
303
  if self.compressor:
316
304
  stored_state = self.compressor.decompress(stored_state)
317
- event_state: Dict[str, Any] = self.transcoder.decode(stored_state)
305
+ event_state: dict[str, Any] = self.transcoder.decode(stored_state)
318
306
  event_state["originator_id"] = stored_event.originator_id
319
307
  event_state["originator_version"] = stored_event.originator_version
320
308
  cls = resolve_topic(stored_event.topic)
@@ -422,7 +410,7 @@ class AggregateRecorder(ABC):
422
410
 
423
411
  @abstractmethod
424
412
  def insert_events(
425
- self, stored_events: List[StoredEvent], **kwargs: Any
413
+ self, stored_events: list[StoredEvent], **kwargs: Any
426
414
  ) -> Sequence[int] | None:
427
415
  """
428
416
  Writes stored events into database.
@@ -437,7 +425,7 @@ class AggregateRecorder(ABC):
437
425
  lte: int | None = None,
438
426
  desc: bool = False,
439
427
  limit: int | None = None,
440
- ) -> List[StoredEvent]:
428
+ ) -> list[StoredEvent]:
441
429
  """
442
430
  Reads stored events from database.
443
431
  """
@@ -468,7 +456,7 @@ class ApplicationRecorder(AggregateRecorder):
468
456
  topics: Sequence[str] = (),
469
457
  *,
470
458
  inclusive_of_start: bool = True,
471
- ) -> List[Notification]:
459
+ ) -> list[Notification]:
472
460
  """
473
461
  Returns a list of Notification objects representing events from an
474
462
  application sequence. If `inclusive_of_start` is True (the default),
@@ -598,7 +586,7 @@ class EventStore:
598
586
 
599
587
  def put(
600
588
  self, domain_events: Sequence[DomainEventProtocol], **kwargs: Any
601
- ) -> List[Recording]:
589
+ ) -> list[Recording]:
602
590
  """
603
591
  Stores domain events in aggregate sequence.
604
592
  """
@@ -670,14 +658,14 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
670
658
 
671
659
  @classmethod
672
660
  def construct(
673
- cls: Type[TInfrastructureFactory], env: Environment | None = None
661
+ cls: type[TInfrastructureFactory], env: Environment | None = None
674
662
  ) -> TInfrastructureFactory:
675
663
  """
676
664
  Constructs concrete infrastructure factory for given
677
665
  named application. Reads and resolves persistence
678
666
  topic from environment variable 'PERSISTENCE_MODULE'.
679
667
  """
680
- factory_cls: Type[InfrastructureFactory[TrackingRecorder]]
668
+ factory_cls: type[InfrastructureFactory[TrackingRecorder]]
681
669
  if env is None:
682
670
  env = Environment()
683
671
  topic = (
@@ -696,7 +684,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
696
684
  or "eventsourcing.popo"
697
685
  )
698
686
  try:
699
- obj: Type[InfrastructureFactory[TrackingRecorder]] | ModuleType = (
687
+ obj: type[InfrastructureFactory[TrackingRecorder]] | ModuleType = (
700
688
  resolve_topic(topic)
701
689
  )
702
690
  except TopicError as e:
@@ -709,15 +697,20 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
709
697
 
710
698
  if isinstance(obj, ModuleType):
711
699
  # Find the factory in the module.
712
- factory_classes: List[Type[InfrastructureFactory[TrackingRecorder]]] = []
700
+ factory_classes: list[type[InfrastructureFactory[TrackingRecorder]]] = []
713
701
  for member in obj.__dict__.values():
714
702
  if (
715
703
  member is not InfrastructureFactory
716
704
  and isinstance(member, type) # Look for classes...
705
+ and isinstance(member, type) # Look for classes...
706
+ and not isinstance(
707
+ member, GenericAlias
708
+ ) # Issue with Python 3.9 and 3.10.
717
709
  and issubclass(member, InfrastructureFactory) # Ignore base class.
718
710
  and member not in factory_classes # Forgive aliases.
719
711
  ):
720
712
  factory_classes.append(member)
713
+
721
714
  if len(factory_classes) == 1:
722
715
  factory_cls = factory_classes[0]
723
716
  else:
@@ -747,7 +740,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
747
740
  """
748
741
  transcoder_topic = self.env.get(self.TRANSCODER_TOPIC)
749
742
  if transcoder_topic:
750
- transcoder_class: Type[Transcoder] = resolve_topic(transcoder_topic)
743
+ transcoder_class: type[Transcoder] = resolve_topic(transcoder_topic)
751
744
  else:
752
745
  transcoder_class = JSONTranscoder
753
746
  return transcoder_class()
@@ -755,7 +748,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
755
748
  def mapper(
756
749
  self,
757
750
  transcoder: Transcoder | None = None,
758
- mapper_class: Type[Mapper] | None = None,
751
+ mapper_class: type[Mapper] | None = None,
759
752
  ) -> Mapper:
760
753
  """
761
754
  Constructs a mapper.
@@ -783,7 +776,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
783
776
  cipher_topic = default_cipher_topic
784
777
 
785
778
  if cipher_topic:
786
- cipher_cls: Type[Cipher] = resolve_topic(cipher_topic)
779
+ cipher_cls: type[Cipher] = resolve_topic(cipher_topic)
787
780
  cipher = cipher_cls(self.env)
788
781
 
789
782
  return cipher
@@ -796,7 +789,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
796
789
  compressor: Compressor | None = None
797
790
  compressor_topic = self.env.get(self.COMPRESSOR_TOPIC)
798
791
  if compressor_topic:
799
- compressor_cls: Type[Compressor] | Compressor = resolve_topic(
792
+ compressor_cls: type[Compressor] | Compressor = resolve_topic(
800
793
  compressor_topic
801
794
  )
802
795
  if isinstance(compressor_cls, type):
@@ -832,7 +825,7 @@ class InfrastructureFactory(ABC, Generic[TTrackingRecorder]):
832
825
 
833
826
  @abstractmethod
834
827
  def tracking_recorder(
835
- self, tracking_recorder_class: Type[TTrackingRecorder] | None = None
828
+ self, tracking_recorder_class: type[TTrackingRecorder] | None = None
836
829
  ) -> TTrackingRecorder:
837
830
  """
838
831
  Constructs a tracking recorder.
@@ -1013,7 +1006,7 @@ class ConnectionPool(ABC, Generic[TConnection]):
1013
1006
  self.max_age = max_age
1014
1007
  self.pre_ping = pre_ping
1015
1008
  self._pool: deque[TConnection] = deque()
1016
- self._in_use: Dict[int, TConnection] = {}
1009
+ self._in_use: dict[int, TConnection] = {}
1017
1010
  self._get_semaphore = Semaphore()
1018
1011
  self._put_condition = Condition()
1019
1012
  self._no_readers = Condition()
@@ -1362,9 +1355,9 @@ class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
1362
1355
  ) -> None:
1363
1356
  super().__init__(recorder=recorder, gt=gt, topics=topics)
1364
1357
  self._select_limit = 500
1365
- self._notifications: List[Notification] = []
1358
+ self._notifications: list[Notification] = []
1366
1359
  self._notifications_index: int = 0
1367
- self._notifications_queue: Queue[List[Notification]] = Queue(maxsize=10)
1360
+ self._notifications_queue: Queue[list[Notification]] = Queue(maxsize=10)
1368
1361
  self._has_been_notified = Event()
1369
1362
  self._thread_error: BaseException | None = None
1370
1363
  self._pull_thread = Thread(target=self._loop_on_pull)
eventsourcing/popo.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import contextlib
4
4
  from collections import defaultdict
5
5
  from threading import Event, Lock
6
- from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence, Set, Type
6
+ from typing import TYPE_CHECKING, Any
7
7
 
8
8
  from eventsourcing.persistence import (
9
9
  AggregateRecorder,
@@ -21,6 +21,7 @@ from eventsourcing.persistence import (
21
21
  from eventsourcing.utils import resolve_topic, reversed_keys
22
22
 
23
23
  if TYPE_CHECKING:
24
+ from collections.abc import Iterable, Sequence
24
25
  from uuid import UUID
25
26
 
26
27
 
@@ -32,23 +33,23 @@ class POPORecorder:
32
33
  class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
33
34
  def __init__(self) -> None:
34
35
  super().__init__()
35
- self._stored_events: List[StoredEvent] = []
36
- self._stored_events_index: Dict[UUID, Dict[int, int]] = defaultdict(dict)
36
+ self._stored_events: list[StoredEvent] = []
37
+ self._stored_events_index: dict[UUID, dict[int, int]] = defaultdict(dict)
37
38
 
38
39
  def insert_events(
39
- self, stored_events: List[StoredEvent], **kwargs: Any
40
+ self, stored_events: list[StoredEvent], **kwargs: Any
40
41
  ) -> Sequence[int] | None:
41
42
  self._insert_events(stored_events, **kwargs)
42
43
  return None
43
44
 
44
45
  def _insert_events(
45
- self, stored_events: List[StoredEvent], **kwargs: Any
46
+ self, stored_events: list[StoredEvent], **kwargs: Any
46
47
  ) -> Sequence[int] | None:
47
48
  with self._database_lock:
48
49
  self._assert_uniqueness(stored_events, **kwargs)
49
50
  return self._update_table(stored_events, **kwargs)
50
51
 
51
- def _assert_uniqueness(self, stored_events: List[StoredEvent], **_: Any) -> None:
52
+ def _assert_uniqueness(self, stored_events: list[StoredEvent], **_: Any) -> None:
52
53
  new = set()
53
54
  for s in stored_events:
54
55
  # Check events don't already exist.
@@ -62,7 +63,7 @@ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
62
63
  raise IntegrityError(msg)
63
64
 
64
65
  def _update_table(
65
- self, stored_events: List[StoredEvent], **_: Any
66
+ self, stored_events: list[StoredEvent], **_: Any
66
67
  ) -> Sequence[int] | None:
67
68
  notification_ids = []
68
69
  for s in stored_events:
@@ -81,7 +82,7 @@ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
81
82
  lte: int | None = None,
82
83
  desc: bool = False,
83
84
  limit: int | None = None,
84
- ) -> List[StoredEvent]:
85
+ ) -> list[StoredEvent]:
85
86
  with self._database_lock:
86
87
  results = []
87
88
 
@@ -103,10 +104,10 @@ class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
103
104
  class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
104
105
  def __init__(self) -> None:
105
106
  super().__init__()
106
- self._listeners: Set[Event] = set()
107
+ self._listeners: set[Event] = set()
107
108
 
108
109
  def insert_events(
109
- self, stored_events: List[StoredEvent], **kwargs: Any
110
+ self, stored_events: list[StoredEvent], **kwargs: Any
110
111
  ) -> Sequence[int] | None:
111
112
  notification_ids = self._insert_events(stored_events, **kwargs)
112
113
  self._notify_listeners()
@@ -120,7 +121,7 @@ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
120
121
  topics: Sequence[str] = (),
121
122
  *,
122
123
  inclusive_of_start: bool = True,
123
- ) -> List[Notification]:
124
+ ) -> list[Notification]:
124
125
  with self._database_lock:
125
126
  results = []
126
127
  if start is None:
@@ -192,8 +193,8 @@ class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
192
193
  class POPOTrackingRecorder(POPORecorder, TrackingRecorder):
193
194
  def __init__(self) -> None:
194
195
  super().__init__()
195
- self._tracking_table: Dict[str, set[int]] = defaultdict(set)
196
- self._max_tracking_ids: Dict[str, int | None] = defaultdict(lambda: None)
196
+ self._tracking_table: dict[str, set[int]] = defaultdict(set)
197
+ self._max_tracking_ids: dict[str, int | None] = defaultdict(lambda: None)
197
198
 
198
199
  def _assert_tracking_uniqueness(self, tracking: Tracking) -> None:
199
200
  if tracking.notification_id in self._tracking_table[tracking.application_name]:
@@ -227,7 +228,7 @@ class POPOProcessRecorder(
227
228
  POPOTrackingRecorder, POPOApplicationRecorder, ProcessRecorder
228
229
  ):
229
230
  def _assert_uniqueness(
230
- self, stored_events: List[StoredEvent], **kwargs: Any
231
+ self, stored_events: list[StoredEvent], **kwargs: Any
231
232
  ) -> None:
232
233
  super()._assert_uniqueness(stored_events, **kwargs)
233
234
  t: Tracking | None = kwargs.get("tracking", None)
@@ -235,7 +236,7 @@ class POPOProcessRecorder(
235
236
  self._assert_tracking_uniqueness(t)
236
237
 
237
238
  def _update_table(
238
- self, stored_events: List[StoredEvent], **kwargs: Any
239
+ self, stored_events: list[StoredEvent], **kwargs: Any
239
240
  ) -> Sequence[int] | None:
240
241
  notification_ids = super()._update_table(stored_events, **kwargs)
241
242
  t: Tracking | None = kwargs.get("tracking", None)
@@ -252,7 +253,7 @@ class POPOFactory(InfrastructureFactory[POPOTrackingRecorder]):
252
253
  return POPOApplicationRecorder()
253
254
 
254
255
  def tracking_recorder(
255
- self, tracking_recorder_class: Type[POPOTrackingRecorder] | None = None
256
+ self, tracking_recorder_class: type[POPOTrackingRecorder] | None = None
256
257
  ) -> POPOTrackingRecorder:
257
258
  if tracking_recorder_class is None:
258
259
  tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
eventsourcing/postgres.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
4
  from asyncio import CancelledError
5
5
  from contextlib import contextmanager
6
6
  from threading import Thread
7
- from typing import TYPE_CHECKING, Any, Callable, Iterator, List, Sequence, Type
7
+ from typing import TYPE_CHECKING, Any, Callable
8
8
 
9
9
  import psycopg
10
10
  import psycopg.errors
@@ -37,6 +37,7 @@ from eventsourcing.persistence import (
37
37
  from eventsourcing.utils import Environment, resolve_topic, retry, strtobool
38
38
 
39
39
  if TYPE_CHECKING:
40
+ from collections.abc import Iterator, Sequence
40
41
  from uuid import UUID
41
42
 
42
43
  from typing_extensions import Self
@@ -190,7 +191,7 @@ class PostgresRecorder:
190
191
  self.datastore = datastore
191
192
  self.create_table_statements = self.construct_create_table_statements()
192
193
 
193
- def construct_create_table_statements(self) -> List[str]:
194
+ def construct_create_table_statements(self) -> list[str]:
194
195
  return []
195
196
 
196
197
  @staticmethod
@@ -247,11 +248,11 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
247
248
  self.select_events_statement = (
248
249
  f"SELECT * FROM {self.events_table_name} WHERE originator_id = %s"
249
250
  )
250
- self.lock_table_statements: List[str] = []
251
+ self.lock_table_statements: list[str] = []
251
252
 
252
253
  @retry((InterfaceError, OperationalError), max_attempts=10, wait=0.2)
253
254
  def insert_events(
254
- self, stored_events: List[StoredEvent], **kwargs: Any
255
+ self, stored_events: list[StoredEvent], **kwargs: Any
255
256
  ) -> Sequence[int] | None:
256
257
  conn: Connection[DictRow]
257
258
  exc: Exception | None = None
@@ -283,7 +284,7 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
283
284
  def _insert_events(
284
285
  self,
285
286
  c: Cursor[DictRow],
286
- stored_events: List[StoredEvent],
287
+ stored_events: list[StoredEvent],
287
288
  **_: Any,
288
289
  ) -> None:
289
290
  pass
@@ -291,7 +292,7 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
291
292
  def _insert_stored_events(
292
293
  self,
293
294
  c: Cursor[DictRow],
294
- stored_events: List[StoredEvent],
295
+ stored_events: list[StoredEvent],
295
296
  **_: Any,
296
297
  ) -> None:
297
298
  # Only do something if there is something to do.
@@ -324,7 +325,7 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
324
325
  def _fetch_ids_after_insert_events(
325
326
  self,
326
327
  c: Cursor[DictRow],
327
- stored_events: List[StoredEvent],
328
+ stored_events: list[StoredEvent],
328
329
  **kwargs: Any,
329
330
  ) -> Sequence[int] | None:
330
331
  return None
@@ -338,9 +339,9 @@ class PostgresAggregateRecorder(PostgresRecorder, AggregateRecorder):
338
339
  lte: int | None = None,
339
340
  desc: bool = False,
340
341
  limit: int | None = None,
341
- ) -> List[StoredEvent]:
342
+ ) -> list[StoredEvent]:
342
343
  statement = self.select_events_statement
343
- params: List[Any] = [originator_id]
344
+ params: list[Any] = [originator_id]
344
345
  if gt is not None:
345
346
  params.append(gt)
346
347
  statement += " AND originator_version > %s"
@@ -413,13 +414,13 @@ class PostgresApplicationRecorder(PostgresAggregateRecorder, ApplicationRecorder
413
414
  topics: Sequence[str] = (),
414
415
  *,
415
416
  inclusive_of_start: bool = True,
416
- ) -> List[Notification]:
417
+ ) -> list[Notification]:
417
418
  """
418
419
  Returns a list of event notifications
419
420
  from 'start', limited by 'limit'.
420
421
  """
421
422
 
422
- params: List[int | str | Sequence[str]] = []
423
+ params: list[int | str | Sequence[str]] = []
423
424
  statement = f"SELECT * FROM {self.events_table_name}"
424
425
  has_where = False
425
426
  if start is not None:
@@ -508,10 +509,10 @@ class PostgresApplicationRecorder(PostgresAggregateRecorder, ApplicationRecorder
508
509
  def _fetch_ids_after_insert_events(
509
510
  self,
510
511
  c: Cursor[DictRow],
511
- stored_events: List[StoredEvent],
512
+ stored_events: list[StoredEvent],
512
513
  **kwargs: Any,
513
514
  ) -> Sequence[int] | None:
514
- notification_ids: List[int] = []
515
+ notification_ids: list[int] = []
515
516
  len_events = len(stored_events)
516
517
  if len_events:
517
518
  while c.nextset() and len(notification_ids) != len_events:
@@ -667,7 +668,7 @@ class PostgresProcessRecorder(
667
668
  def _insert_events(
668
669
  self,
669
670
  c: Cursor[DictRow],
670
- stored_events: List[StoredEvent],
671
+ stored_events: list[StoredEvent],
671
672
  **kwargs: Any,
672
673
  ) -> None:
673
674
  tracking: Tracking | None = kwargs.get("tracking", None)
@@ -893,7 +894,7 @@ class PostgresFactory(InfrastructureFactory[PostgresTrackingRecorder]):
893
894
 
894
895
  application_recorder_topic = self.env.get(self.APPLICATION_RECORDER_TOPIC)
895
896
  if application_recorder_topic:
896
- application_recorder_class: Type[PostgresApplicationRecorder] = (
897
+ application_recorder_class: type[PostgresApplicationRecorder] = (
897
898
  resolve_topic(application_recorder_topic)
898
899
  )
899
900
  assert issubclass(application_recorder_class, PostgresApplicationRecorder)
@@ -909,7 +910,7 @@ class PostgresFactory(InfrastructureFactory[PostgresTrackingRecorder]):
909
910
  return recorder
910
911
 
911
912
  def tracking_recorder(
912
- self, tracking_recorder_class: Type[PostgresTrackingRecorder] | None = None
913
+ self, tracking_recorder_class: type[PostgresTrackingRecorder] | None = None
913
914
  ) -> PostgresTrackingRecorder:
914
915
  prefix = self.env.name.lower() or "notification"
915
916
  tracking_table_name = prefix + "_tracking"
@@ -942,7 +943,7 @@ class PostgresFactory(InfrastructureFactory[PostgresTrackingRecorder]):
942
943
 
943
944
  process_recorder_topic = self.env.get(self.PROCESS_RECORDER_TOPIC)
944
945
  if process_recorder_topic:
945
- process_recorder_class: Type[PostgresTrackingRecorder] = resolve_topic(
946
+ process_recorder_class: type[PostgresTrackingRecorder] = resolve_topic(
946
947
  process_recorder_topic
947
948
  )
948
949
  assert issubclass(process_recorder_class, PostgresProcessRecorder)
@@ -3,19 +3,10 @@ from __future__ import annotations
3
3
  import os
4
4
  import weakref
5
5
  from abc import ABC, abstractmethod
6
+ from collections.abc import Iterator, Sequence
6
7
  from threading import Event, Thread
7
8
  from traceback import format_exc
8
- from typing import (
9
- TYPE_CHECKING,
10
- Any,
11
- Dict,
12
- Generic,
13
- Iterator,
14
- Sequence,
15
- Tuple,
16
- Type,
17
- TypeVar,
18
- )
9
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
19
10
  from warnings import warn
20
11
 
21
12
  from eventsourcing.application import Application
@@ -34,7 +25,7 @@ if TYPE_CHECKING:
34
25
  from typing_extensions import Self
35
26
 
36
27
 
37
- class ApplicationSubscription(Iterator[Tuple[DomainEventProtocol, Tracking]]):
28
+ class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
38
29
  """
39
30
  An iterator that yields all domain events recorded in an application
40
31
  sequence that have notification IDs greater than a given value. The iterator
@@ -64,7 +55,7 @@ class ApplicationSubscription(Iterator[Tuple[DomainEventProtocol, Tracking]]):
64
55
  def __iter__(self) -> Self:
65
56
  return self
66
57
 
67
- def __next__(self) -> Tuple[DomainEventProtocol, Tracking]:
58
+ def __next__(self) -> tuple[DomainEventProtocol, Tracking]:
68
59
  notification = next(self.subscription)
69
60
  tracking = Tracking(self.name, notification.id)
70
61
  domain_event = self.mapper.to_domain_event(notification)
@@ -109,9 +100,9 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
109
100
  def __init__(
110
101
  self,
111
102
  *,
112
- application_class: Type[TApplication],
113
- projection_class: Type[Projection[TTrackingRecorder]],
114
- tracking_recorder_class: Type[TTrackingRecorder] | None = None,
103
+ application_class: type[TApplication],
104
+ projection_class: type[Projection[TTrackingRecorder]],
105
+ tracking_recorder_class: type[TTrackingRecorder] | None = None,
115
106
  env: EnvType | None = None,
116
107
  ):
117
108
  self.app: TApplication = application_class(env)
@@ -134,14 +125,14 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
134
125
  gt=self.tracking_recorder.max_tracking_id(self.app.name),
135
126
  topics=self.projection.topics,
136
127
  )
137
- self._has_error = Event()
128
+ self._is_stopping = Event()
138
129
  self.thread_error: BaseException | None = None
139
130
  self.processing_thread = Thread(
140
131
  target=self._process_events_loop,
141
132
  kwargs={
142
133
  "subscription": self.subscription,
143
134
  "projection": self.projection,
144
- "has_error": self._has_error,
135
+ "is_stopping": self._is_stopping,
145
136
  "runner": weakref.ref(self),
146
137
  },
147
138
  )
@@ -151,20 +142,21 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
151
142
  """
152
143
  Constructs environment from which projection will be configured.
153
144
  """
154
- _env: Dict[str, str] = {}
145
+ _env: dict[str, str] = {}
155
146
  _env.update(os.environ)
156
147
  if env is not None:
157
148
  _env.update(env)
158
149
  return Environment(name, _env)
159
150
 
160
151
  def stop(self) -> None:
152
+ self._is_stopping.set()
161
153
  self.subscription.stop()
162
154
 
163
155
  @staticmethod
164
156
  def _process_events_loop(
165
157
  subscription: ApplicationSubscription,
166
158
  projection: Projection[TrackingRecorder],
167
- has_error: Event,
159
+ is_stopping: Event,
168
160
  runner: weakref.ReferenceType[ProjectionRunner[Application, TrackingRecorder]],
169
161
  ) -> None:
170
162
  try:
@@ -184,12 +176,11 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
184
176
  stacklevel=2,
185
177
  )
186
178
 
187
- has_error.set()
179
+ is_stopping.set()
188
180
  subscription.subscription.stop()
189
181
 
190
182
  def run_forever(self, timeout: float | None = None) -> None:
191
- if self._has_error.wait(timeout=timeout):
192
- assert self.thread_error is not None # for mypy
183
+ if self._is_stopping.wait(timeout=timeout) and self.thread_error is not None:
193
184
  raise self.thread_error
194
185
 
195
186
  def wait(self, notification_id: int, timeout: float = 1.0) -> None:
@@ -198,11 +189,11 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
198
189
  application_name=self.subscription.name,
199
190
  notification_id=notification_id,
200
191
  timeout=timeout,
201
- interrupt=self._has_error,
192
+ interrupt=self._is_stopping,
202
193
  )
203
194
  except WaitInterruptedError:
204
- assert self.thread_error is not None # for mypy
205
- raise self.thread_error from None
195
+ if self.thread_error is not None:
196
+ raise self.thread_error from None
206
197
 
207
198
  def __enter__(self) -> Self:
208
199
  return self