eventsourcing 9.4.0a2__tar.gz → 9.4.0a4__tar.gz

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 (26) hide show
  1. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/PKG-INFO +2 -2
  2. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/README.md +1 -1
  3. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/persistence.py +14 -4
  4. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/popo.py +10 -4
  5. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/postgres.py +10 -4
  6. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/projection.py +20 -5
  7. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/sqlite.py +3 -1
  8. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/tests/persistence.py +39 -0
  9. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/pyproject.toml +1 -1
  10. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/AUTHORS +0 -0
  11. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/LICENSE +0 -0
  12. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/__init__.py +0 -0
  13. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/application.py +0 -0
  14. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/cipher.py +0 -0
  15. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/compressor.py +0 -0
  16. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/cryptography.py +0 -0
  17. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/dispatch.py +0 -0
  18. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/domain.py +0 -0
  19. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/interface.py +0 -0
  20. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/py.typed +0 -0
  21. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/system.py +0 -0
  22. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/tests/__init__.py +0 -0
  23. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/tests/application.py +0 -0
  24. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/tests/domain.py +0 -0
  25. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/tests/postgres_utils.py +0 -0
  26. {eventsourcing-9.4.0a2 → eventsourcing-9.4.0a4}/eventsourcing/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eventsourcing
3
- Version: 9.4.0a2
3
+ Version: 9.4.0a4
4
4
  Summary: Event sourcing in Python
5
5
  Home-page: https://github.com/pyeventsourcing/eventsourcing
6
6
  License: BSD 3-Clause
@@ -35,7 +35,7 @@ Requires-Dist: typing_extensions
35
35
  Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
36
36
  Description-Content-Type: text/markdown
37
37
 
38
- [![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg)](https://github.com/pyeventsourcing/eventsourcing)
38
+ [![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg?branch=9.4)](https://github.com/pyeventsourcing/eventsourcing)
39
39
  [![Coverage Status](https://coveralls.io/repos/github/pyeventsourcing/eventsourcing/badge.svg?branch=main)](https://coveralls.io/github/pyeventsourcing/eventsourcing?branch=main)
40
40
  [![Documentation Status](https://readthedocs.org/projects/eventsourcing/badge/?version=stable)](https://eventsourcing.readthedocs.io/en/stable/)
41
41
  [![Latest Release](https://badge.fury.io/py/eventsourcing.svg)](https://pypi.org/project/eventsourcing/)
@@ -1,4 +1,4 @@
1
- [![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg)](https://github.com/pyeventsourcing/eventsourcing)
1
+ [![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg?branch=9.4)](https://github.com/pyeventsourcing/eventsourcing)
2
2
  [![Coverage Status](https://coveralls.io/repos/github/pyeventsourcing/eventsourcing/badge.svg?branch=main)](https://coveralls.io/github/pyeventsourcing/eventsourcing?branch=main)
3
3
  [![Documentation Status](https://readthedocs.org/projects/eventsourcing/badge/?version=stable)](https://eventsourcing.readthedocs.io/en/stable/)
4
4
  [![Latest Release](https://badge.fury.io/py/eventsourcing.svg)](https://pypi.org/project/eventsourcing/)
@@ -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:
@@ -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:
@@ -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,12 +126,13 @@ 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),
118
- )
119
- self.projection = projection_class(
120
- tracking_recorder=self.tracking_recorder,
135
+ topics=self.projection.topics,
121
136
  )
122
137
  self._has_error = Event()
123
138
  self.thread_error: BaseException | None = None
@@ -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
 
@@ -707,6 +707,45 @@ class ApplicationRecorderTestCase(TestCase, ABC):
707
707
  stored_event3.originator_id, notifications[0].originator_id
708
708
  )
709
709
 
710
+ # Start a subscription, call stop() during iteration.
711
+ with recorder.subscribe(gt=None) as subscription:
712
+
713
+ # Receive events from the subscription.
714
+ for i, _ in enumerate(subscription):
715
+ subscription.stop()
716
+ # Shouldn't get here twice...
717
+ self.assertLess(i, 1, "Got here twice")
718
+
719
+ # Start a subscription, call stop() before iteration.
720
+ subscription = recorder.subscribe(gt=None)
721
+ with subscription:
722
+ subscription.stop()
723
+ # Receive events from the subscription.
724
+ for _ in subscription:
725
+ # Shouldn't get here...
726
+ self.fail("Got here")
727
+
728
+ # Start a subscription, call stop() before entering context manager.
729
+ subscription = recorder.subscribe(gt=None)
730
+ subscription.stop()
731
+ with subscription:
732
+ # Receive events from the subscription.
733
+ for _ in subscription:
734
+ # Shouldn't get here...
735
+ self.fail("Got here")
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
+
710
749
  def close_db_connection(self, *args: Any) -> None:
711
750
  """"""
712
751
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "eventsourcing"
3
- version = "9.4.0a2"
3
+ version = "9.4.0a4"
4
4
 
5
5
  description = "Event sourcing in Python"
6
6
  authors = [
File without changes
File without changes