eventsourcing 9.4.0a3__py3-none-any.whl → 9.4.0a5__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.

@@ -486,7 +486,9 @@ class ApplicationRecorder(AggregateRecorder):
486
486
  """
487
487
 
488
488
  @abstractmethod
489
- def subscribe(self, gt: int | None = None) -> Subscription[ApplicationRecorder]:
489
+ def subscribe(
490
+ self, gt: int | None = None, topics: Sequence[str] = ()
491
+ ) -> Subscription[ApplicationRecorder]:
490
492
  """
491
493
  Returns an iterator of Notification objects representing events from an
492
494
  application sequence.
@@ -1311,10 +1313,14 @@ TApplicationRecorder_co = TypeVar(
1311
1313
 
1312
1314
  class Subscription(Iterator[Notification], Generic[TApplicationRecorder_co]):
1313
1315
  def __init__(
1314
- self, recorder: TApplicationRecorder_co, gt: int | None = None
1316
+ self,
1317
+ recorder: TApplicationRecorder_co,
1318
+ gt: int | None = None,
1319
+ topics: Sequence[str] = (),
1315
1320
  ) -> None:
1316
1321
  self._recorder = recorder
1317
1322
  self._last_notification_id = gt
1323
+ self._topics = topics
1318
1324
  self._has_been_entered = False
1319
1325
  self._has_been_stopped = False
1320
1326
 
@@ -1349,9 +1355,12 @@ class Subscription(Iterator[Notification], Generic[TApplicationRecorder_co]):
1349
1355
 
1350
1356
  class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
1351
1357
  def __init__(
1352
- self, recorder: TApplicationRecorder_co, gt: int | None = None
1358
+ self,
1359
+ recorder: TApplicationRecorder_co,
1360
+ gt: int | None = None,
1361
+ topics: Sequence[str] = (),
1353
1362
  ) -> None:
1354
- super().__init__(recorder=recorder, gt=gt)
1363
+ super().__init__(recorder=recorder, gt=gt, topics=topics)
1355
1364
  self._select_limit = 500
1356
1365
  self._notifications: List[Notification] = []
1357
1366
  self._notifications_index: int = 0
@@ -1410,6 +1419,7 @@ class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
1410
1419
  notifications = self._recorder.select_notifications(
1411
1420
  start=self._last_notification_id or 0,
1412
1421
  limit=self._select_limit,
1422
+ topics=self._topics,
1413
1423
  inclusive_of_start=False,
1414
1424
  )
1415
1425
  if len(notifications) > 0:
eventsourcing/popo.py CHANGED
@@ -14,6 +14,7 @@ from eventsourcing.persistence import (
14
14
  Notification,
15
15
  ProcessRecorder,
16
16
  StoredEvent,
17
+ Subscription,
17
18
  Tracking,
18
19
  TrackingRecorder,
19
20
  )
@@ -155,8 +156,10 @@ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
155
156
  with self._database_lock:
156
157
  return len(self._stored_events) or None
157
158
 
158
- def subscribe(self, gt: int | None = None) -> POPOSubscription:
159
- return POPOSubscription(self, gt)
159
+ def subscribe(
160
+ self, gt: int | None = None, topics: Sequence[str] = ()
161
+ ) -> Subscription[ApplicationRecorder]:
162
+ return POPOSubscription(recorder=self, gt=gt, topics=topics)
160
163
 
161
164
  def listen(self, event: Event) -> None:
162
165
  self._listeners.add(event)
@@ -172,10 +175,13 @@ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
172
175
 
173
176
  class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
174
177
  def __init__(
175
- self, recorder: POPOApplicationRecorder, gt: int | None = None
178
+ self,
179
+ recorder: POPOApplicationRecorder,
180
+ gt: int | None = None,
181
+ topics: Sequence[str] = (),
176
182
  ) -> None:
177
183
  assert isinstance(recorder, POPOApplicationRecorder)
178
- super().__init__(recorder=recorder, gt=gt)
184
+ super().__init__(recorder=recorder, gt=gt, topics=topics)
179
185
  self._recorder.listen(self._has_been_notified)
180
186
 
181
187
  def stop(self) -> None:
eventsourcing/postgres.py CHANGED
@@ -30,6 +30,7 @@ from eventsourcing.persistence import (
30
30
  ProcessRecorder,
31
31
  ProgrammingError,
32
32
  StoredEvent,
33
+ Subscription,
33
34
  Tracking,
34
35
  TrackingRecorder,
35
36
  )
@@ -524,16 +525,21 @@ class PostgresApplicationRecorder(PostgresAggregateRecorder, ApplicationRecorder
524
525
  raise ProgrammingError(msg)
525
526
  return notification_ids
526
527
 
527
- def subscribe(self, gt: int | None = None) -> PostgresSubscription:
528
- return PostgresSubscription(self, gt)
528
+ def subscribe(
529
+ self, gt: int | None = None, topics: Sequence[str] = ()
530
+ ) -> Subscription[ApplicationRecorder]:
531
+ return PostgresSubscription(recorder=self, gt=gt, topics=topics)
529
532
 
530
533
 
531
534
  class PostgresSubscription(ListenNotifySubscription[PostgresApplicationRecorder]):
532
535
  def __init__(
533
- self, recorder: PostgresApplicationRecorder, gt: int | None = None
536
+ self,
537
+ recorder: PostgresApplicationRecorder,
538
+ gt: int | None = None,
539
+ topics: Sequence[str] = (),
534
540
  ) -> None:
535
541
  assert isinstance(recorder, PostgresApplicationRecorder)
536
- super().__init__(recorder=recorder, gt=gt)
542
+ super().__init__(recorder=recorder, gt=gt, topics=topics)
537
543
  self._listen_thread = Thread(target=self._listen)
538
544
  self._listen_thread.start()
539
545
 
@@ -5,7 +5,17 @@ import weakref
5
5
  from abc import ABC, abstractmethod
6
6
  from threading import Event, Thread
7
7
  from traceback import format_exc
8
- from typing import TYPE_CHECKING, Any, Dict, Generic, Iterator, Tuple, Type, TypeVar
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
19
  from warnings import warn
10
20
 
11
21
  from eventsourcing.application import Application
@@ -37,11 +47,12 @@ class ApplicationSubscription(Iterator[Tuple[DomainEventProtocol, Tracking]]):
37
47
  self,
38
48
  app: Application,
39
49
  gt: int | None = None,
50
+ topics: Sequence[str] = (),
40
51
  ):
41
52
  self.name = app.name
42
53
  self.recorder = app.recorder
43
54
  self.mapper = app.mapper
44
- self.subscription = self.recorder.subscribe(gt=gt)
55
+ self.subscription = self.recorder.subscribe(gt=gt, topics=topics)
45
56
 
46
57
  def __enter__(self) -> Self:
47
58
  self.subscription.__enter__()
@@ -68,6 +79,9 @@ class ApplicationSubscription(Iterator[Tuple[DomainEventProtocol, Tracking]]):
68
79
 
69
80
  class Projection(ABC, Generic[TTrackingRecorder]):
70
81
  name: str = ""
82
+ """Name of projection, used to pick prefixed environment variables."""
83
+ topics: Sequence[str] = ()
84
+ """Event topics, used to filter events in database."""
71
85
 
72
86
  def __init__(
73
87
  self,
@@ -112,21 +126,22 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
112
126
  self.projection_factory.tracking_recorder(tracking_recorder_class)
113
127
  )
114
128
 
129
+ self.projection = projection_class(
130
+ tracking_recorder=self.tracking_recorder,
131
+ )
115
132
  self.subscription = ApplicationSubscription(
116
133
  app=self.app,
117
134
  gt=self.tracking_recorder.max_tracking_id(self.app.name),
135
+ topics=self.projection.topics,
118
136
  )
119
- self.projection = projection_class(
120
- tracking_recorder=self.tracking_recorder,
121
- )
122
- self._has_error = Event()
137
+ self._is_stopping = Event()
123
138
  self.thread_error: BaseException | None = None
124
139
  self.processing_thread = Thread(
125
140
  target=self._process_events_loop,
126
141
  kwargs={
127
142
  "subscription": self.subscription,
128
143
  "projection": self.projection,
129
- "has_error": self._has_error,
144
+ "is_stopping": self._is_stopping,
130
145
  "runner": weakref.ref(self),
131
146
  },
132
147
  )
@@ -143,13 +158,14 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
143
158
  return Environment(name, _env)
144
159
 
145
160
  def stop(self) -> None:
161
+ self._is_stopping.set()
146
162
  self.subscription.stop()
147
163
 
148
164
  @staticmethod
149
165
  def _process_events_loop(
150
166
  subscription: ApplicationSubscription,
151
167
  projection: Projection[TrackingRecorder],
152
- has_error: Event,
168
+ is_stopping: Event,
153
169
  runner: weakref.ReferenceType[ProjectionRunner[Application, TrackingRecorder]],
154
170
  ) -> None:
155
171
  try:
@@ -169,12 +185,11 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
169
185
  stacklevel=2,
170
186
  )
171
187
 
172
- has_error.set()
188
+ is_stopping.set()
173
189
  subscription.subscription.stop()
174
190
 
175
191
  def run_forever(self, timeout: float | None = None) -> None:
176
- if self._has_error.wait(timeout=timeout):
177
- assert self.thread_error is not None # for mypy
192
+ if self._is_stopping.wait(timeout=timeout) and self.thread_error is not None:
178
193
  raise self.thread_error
179
194
 
180
195
  def wait(self, notification_id: int, timeout: float = 1.0) -> None:
@@ -183,11 +198,11 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
183
198
  application_name=self.subscription.name,
184
199
  notification_id=notification_id,
185
200
  timeout=timeout,
186
- interrupt=self._has_error,
201
+ interrupt=self._is_stopping,
187
202
  )
188
203
  except WaitInterruptedError:
189
- assert self.thread_error is not None # for mypy
190
- raise self.thread_error from None
204
+ if self.thread_error is not None:
205
+ raise self.thread_error from None
191
206
 
192
207
  def __enter__(self) -> Self:
193
208
  return self
eventsourcing/sqlite.py CHANGED
@@ -471,7 +471,9 @@ class SQLiteApplicationRecorder(
471
471
  c.execute(self.select_max_notification_id_statement)
472
472
  return c.fetchone()[0]
473
473
 
474
- def subscribe(self, gt: int | None = None) -> Subscription[ApplicationRecorder]:
474
+ def subscribe(
475
+ self, gt: int | None = None, topics: Sequence[str] = ()
476
+ ) -> Subscription[ApplicationRecorder]:
475
477
  msg = f"The {type(self).__qualname__} recorder does not support subscriptions"
476
478
  raise NotImplementedError(msg)
477
479
 
@@ -734,6 +734,18 @@ class ApplicationRecorderTestCase(TestCase, ABC):
734
734
  # Shouldn't get here...
735
735
  self.fail("Got here")
736
736
 
737
+ # Start a subscription with topics.
738
+ subscription = recorder.subscribe(gt=None, topics=["topic3"])
739
+ with subscription:
740
+ for notification in subscription:
741
+ self.assertEqual(notification.topic, "topic3")
742
+ if (
743
+ notification.originator_id == stored_event3.originator_id
744
+ and notification.originator_version
745
+ == stored_event3.originator_version
746
+ ):
747
+ break
748
+
737
749
  def close_db_connection(self, *args: Any) -> None:
738
750
  """"""
739
751
 
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: eventsourcing
3
- Version: 9.4.0a3
3
+ Version: 9.4.0a5
4
4
  Summary: Event sourcing in Python
5
- Home-page: https://github.com/pyeventsourcing/eventsourcing
6
5
  License: BSD 3-Clause
7
6
  Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
8
7
  Author: John Bywater
@@ -32,6 +31,7 @@ Requires-Dist: cryptography (>=44.0,<44.1) ; extra == "cryptography"
32
31
  Requires-Dist: psycopg[pool] (<=3.2.99999) ; extra == "postgres"
33
32
  Requires-Dist: pycryptodome (>=3.22,<3.23) ; extra == "crypto"
34
33
  Requires-Dist: typing_extensions
34
+ Project-URL: Homepage, https://github.com/pyeventsourcing/eventsourcing
35
35
  Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
36
36
  Description-Content-Type: text/markdown
37
37
 
@@ -6,21 +6,21 @@ eventsourcing/cryptography.py,sha256=ZsQFyeyMZysADqKy38ECV71j6EMMSbo3VQO7oRnC1h0
6
6
  eventsourcing/dispatch.py,sha256=yYSpT-jqc6l_wTdqEnfPJJfvsZN2Ta8g2anrVPWIcqQ,1412
7
7
  eventsourcing/domain.py,sha256=pNetJA4uKf1chgfNFKWv1Fke3_V5g0ygRv63WT7nsUc,58208
8
8
  eventsourcing/interface.py,sha256=LIFI9AZhoVWUAq4YjKosGCpinf51jmVLqw1Ii4npSHo,5079
9
- eventsourcing/persistence.py,sha256=h5laiUP4mc6Ur1PoDnNBLXRcl8V-2-ife_hYktlCmmM,45732
10
- eventsourcing/popo.py,sha256=4q4-l8iuEueqvYzGMzyFEiZaT8dm_PJNosmGwzRt1oo,9393
11
- eventsourcing/postgres.py,sha256=h4ETgjzZtj9a-otJkMfzHZSxD_hOUWyVTIVuESVI7y0,36138
12
- eventsourcing/projection.py,sha256=6UNNF_iVYR3nmOfoysgvhtjwYnEBTvsHkQM2gzXPTbQ,6489
9
+ eventsourcing/persistence.py,sha256=sfGq8BV1cnmrTy1OLoWXIObArgUD9wPHcEeM0KwZ5p8,45962
10
+ eventsourcing/popo.py,sha256=wBXoSVg53nVOsZVzKkrCOrXxzWe2HH1mC4aEV97rDdk,9565
11
+ eventsourcing/postgres.py,sha256=wu5b3Kvr4NO9yegCLAkIMDrjpkTVNisCXlFmCb74tAE,36306
12
+ eventsourcing/projection.py,sha256=7wRXY4w8gLNgeFKdlJxo3Q6i7SaBBacbUIEhGda3das,6807
13
13
  eventsourcing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- eventsourcing/sqlite.py,sha256=3bezfl3H9I366NnIwLpn4cz_L3fk0pHNiOSO_Hrq6Xw,21909
14
+ eventsourcing/sqlite.py,sha256=_zY-08IosW1idOnPPRg9rh0C3AyHT6yqwAv4WCOw-dg,21951
15
15
  eventsourcing/system.py,sha256=7cM3FBdvr64ZK_xItks1MW2yg1o0OZ1vtKtznFFB_4g,47114
16
16
  eventsourcing/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  eventsourcing/tests/application.py,sha256=Q_NFgRSRWovMcyQOZ8U1AoiIsnEHf_zCpHcP0ADaYvs,17369
18
18
  eventsourcing/tests/domain.py,sha256=lHSlY6jIoSeqlcPSbrrozEPUJGvJ8bgPrznlmzTxn2w,3254
19
- eventsourcing/tests/persistence.py,sha256=5y2675k-e-A3JjPVw4EVNSF-Oem96CVWMOMft_1NK-c,56601
19
+ eventsourcing/tests/persistence.py,sha256=M1EZPs8DhAOW8N_GSnALPcmz-pNVCJDx73pHaEJa138,57108
20
20
  eventsourcing/tests/postgres_utils.py,sha256=xymcGYasUXeZTBenkHz-ykD8HtrFjVM1Z7-qRrH6OQk,1364
21
21
  eventsourcing/utils.py,sha256=QPlHhltgEcL80RWcPJ_PTygzDwhfIowUS3Z5taw0_cA,8228
22
- eventsourcing-9.4.0a3.dist-info/AUTHORS,sha256=8aHOM4UbNZcKlD-cHpFRcM6RWyCqtwtxRev6DeUgVRs,137
23
- eventsourcing-9.4.0a3.dist-info/LICENSE,sha256=CQEQzcZO8AWXL5i3hIo4yVKrYjh2FBz6hCM7kpXWpw4,1512
24
- eventsourcing-9.4.0a3.dist-info/METADATA,sha256=a4kiNuw1ATJbhUMa-UbYnd5lDI6IH_qBtKuhCeAM7E0,9885
25
- eventsourcing-9.4.0a3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
26
- eventsourcing-9.4.0a3.dist-info/RECORD,,
22
+ eventsourcing-9.4.0a5.dist-info/AUTHORS,sha256=8aHOM4UbNZcKlD-cHpFRcM6RWyCqtwtxRev6DeUgVRs,137
23
+ eventsourcing-9.4.0a5.dist-info/LICENSE,sha256=CQEQzcZO8AWXL5i3hIo4yVKrYjh2FBz6hCM7kpXWpw4,1512
24
+ eventsourcing-9.4.0a5.dist-info/METADATA,sha256=QDNOXf3BTCZYHc2QrlEEg1cUJYZ9DIsCsuNfTeczIaE,9897
25
+ eventsourcing-9.4.0a5.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
26
+ eventsourcing-9.4.0a5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any